Skip to content

Commit e81ddbd

Browse files
committed
feat: one passing test
1 parent 1eec992 commit e81ddbd

File tree

11 files changed

+619
-96
lines changed

11 files changed

+619
-96
lines changed

packages/auth-server/components/account-recovery/guardian-flow/Step4ConfirmNow.vue

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ const { getWalletClient, defaultChain } = useClientStore();
9797
const { isSsoAccount: checkIsSsoAccount, isLoading, error: isSsoAccountError } = useIsSsoAccount();
9898
const { confirmGuardian, confirmGuardianInProgress } = useRecoveryGuardian();
9999
const { getConfigurableAccount, getConfigurableAccountInProgress } = useConfigurableAccount();
100-
const { address } = useAccountStore();
100+
const { address: currentSsoAddress } = useAccountStore();
101101
const accountData = useAppKitAccount();
102102
103103
const confirmGuardianErrorMessage = ref<string | null>(null);
@@ -116,29 +116,52 @@ const handleCheck = async () => {
116116
117117
const handleConfirmGuardian = async () => {
118118
try {
119-
if (!address) {
119+
if (!currentSsoAddress) {
120120
throw new Error("No account logged in");
121121
}
122122
123+
console.log(`[Step4ConfirmNow] Starting confirmation. Account to guard: ${currentSsoAddress}, Guardian address: ${props.guardianAddress}, Is SSO: ${isSsoAccount.value}`);
124+
123125
let client: Parameters<typeof confirmGuardian>[0]["client"];
124126
125-
if (isSsoAccount.value) {
126-
const configurableAccount = await getConfigurableAccount({ address: props.guardianAddress });
127+
// Check if guardian is the currently connected SSO account
128+
const isCurrentSsoGuardian = isSsoAccount.value
129+
&& currentSsoAddress
130+
&& props.guardianAddress.toLowerCase() === currentSsoAddress.toLowerCase();
131+
132+
if (isCurrentSsoGuardian) {
133+
// Guardian is the current SSO account - use SSO client with paymaster
134+
console.log("[Step4ConfirmNow] Getting configurable account for current SSO guardian");
135+
const configurableAccount = await getConfigurableAccount({
136+
address: props.guardianAddress,
137+
usePaymaster: true,
138+
});
127139
if (!configurableAccount) {
140+
console.error(`[Step4ConfirmNow] No configurable account found for ${props.guardianAddress}`);
128141
throw new Error("No configurable account found");
129142
}
143+
console.log("[Step4ConfirmNow] Using SSO client:", configurableAccount.account.address);
130144
client = configurableAccount;
131145
} else {
146+
// Guardian is a different account - use WalletConnect
147+
if (!accountData.value.isConnected) {
148+
throw new Error("Please connect your wallet first");
149+
}
150+
console.log("[Step4ConfirmNow] Getting wallet client for guardian");
132151
client = await getWalletClient({ chainId: defaultChain.id });
152+
console.log("[Step4ConfirmNow] Using wallet client:", client.account.address);
133153
}
134154
155+
console.log(`[Step4ConfirmNow] Calling confirmGuardian with client address: ${client.account.address}`);
135156
await confirmGuardian({
136157
client,
137-
accountToGuard: address,
158+
accountToGuard: currentSsoAddress,
138159
});
160+
console.log("[Step4ConfirmNow] Guardian confirmed successfully");
139161
confirmGuardianErrorMessage.value = null;
140162
emit("next");
141163
} catch (err) {
164+
console.error("[Step4ConfirmNow] Error confirming guardian:", err);
142165
confirmGuardianErrorMessage.value = "An error occurred while confirming the guardian. Please try again.";
143166
// eslint-disable-next-line no-console
144167
console.error(err);

packages/auth-server/composables/useConfigurableAccount.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ import { WebAuthnValidatorAbi } from "zksync-sso-4337/abi";
44
export const useConfigurableAccount = () => {
55
const { getPublicClient, getConfigurableClient, defaultChain, contractsByChain } = useClientStore();
66

7-
const { inProgress: getConfigurableAccountInProgress, error: getConfigurableAccountError, execute: getConfigurableAccount } = useAsync(async ({ address }: { address: Address }) => {
7+
const { inProgress: getConfigurableAccountInProgress, error: getConfigurableAccountError, execute: getConfigurableAccount } = useAsync(async ({ address, usePaymaster = false }: { address: Address; usePaymaster?: boolean }) => {
88
const publicClient = getPublicClient({ chainId: defaultChain.id });
99
const webauthnValidatorAddress = contractsByChain[defaultChain.id].webauthnValidator;
1010

11+
// Get current block to calculate safe fromBlock (avoid RPC block range limits)
12+
const currentBlock = await publicClient.getBlockNumber();
13+
// Query last 100k blocks or from genesis, whichever is more recent
14+
const fromBlock = currentBlock > 100000n ? currentBlock - 100000n : 0n;
15+
1116
// FIXME: events should be scoped to the origin domain
1217
// As well, this doesn't seem to be a reliable way of retrieving a `credentialId`
1318
// but works for now.
@@ -19,7 +24,7 @@ export const useConfigurableAccount = () => {
1924
args: {
2025
keyOwner: address,
2126
},
22-
fromBlock: "earliest",
27+
fromBlock,
2328
strict: true,
2429
}),
2530
publicClient.getContractEvents({
@@ -29,7 +34,7 @@ export const useConfigurableAccount = () => {
2934
args: {
3035
keyOwner: address,
3136
},
32-
fromBlock: "earliest",
37+
fromBlock,
3338
strict: true,
3439
}),
3540
]);
@@ -58,6 +63,7 @@ export const useConfigurableAccount = () => {
5863
chainId: defaultChain.id,
5964
address,
6065
credentialId: credentialIdHex,
66+
usePaymaster,
6167
});
6268
});
6369

packages/auth-server/composables/useIsSsoAccount.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ export function useIsSsoAccount() {
88
const publicClient = getPublicClient({ chainId: defaultChain.id });
99

1010
try {
11-
return await publicClient.readContract({
11+
console.log("[useIsSsoAccount] Checking if address is SSO account:", accountAddress);
12+
console.log("[useIsSsoAccount] SSO Interface ID:", runtimeConfig.public.ssoAccountInterfaceId);
13+
14+
const result = await publicClient.readContract({
1215
address: accountAddress,
1316
abi: [{
1417
type: "function",
@@ -20,8 +23,33 @@ export function useIsSsoAccount() {
2023
functionName: "supportsInterface",
2124
args: [runtimeConfig.public.ssoAccountInterfaceId as Address],
2225
});
23-
;
24-
} catch {
26+
27+
console.log("[useIsSsoAccount] supportsInterface result:", result);
28+
return result;
29+
} catch (err: unknown) {
30+
console.error("[useIsSsoAccount] Error checking SSO account:", err);
31+
32+
// Handle NoFallbackHandler error (0x48c9ceda) - ModularSmartAccount doesn't implement supportsInterface yet
33+
// WORKAROUND: In our dev environment, all accounts deployed via auth-server-api are ERC-4337 ModularSmartAccounts
34+
// that throw this error. We treat these as SSO accounts.
35+
// Check both the error message and the full error string representation
36+
const errorString = err.toString?.() || String(err);
37+
const errorMessage = err.message || "";
38+
39+
console.log("[useIsSsoAccount] DEBUG - Error details:");
40+
console.log(" errorMessage includes 0x48c9ceda?", errorMessage.includes("0x48c9ceda"));
41+
console.log(" errorString includes 0x48c9ceda?", errorString.includes("0x48c9ceda"));
42+
console.log(" errorMessage:", errorMessage.substring(0, 200));
43+
console.log(" errorString:", errorString.substring(0, 200));
44+
45+
if (errorMessage.includes("0x48c9ceda") || errorMessage.includes("NoFallbackHandler")
46+
|| errorString.includes("0x48c9ceda") || errorString.includes("NoFallbackHandler")) {
47+
console.log("[useIsSsoAccount] ✅ MATCHED! Account has no fallback handler - this is a ModularSmartAccount (ERC-4337), treating as SSO");
48+
return true;
49+
}
50+
51+
// For other errors, assume not an SSO account
52+
console.warn("[useIsSsoAccount] Unknown error checking supportsInterface, assuming not SSO");
2553
return false;
2654
}
2755
});

packages/auth-server/composables/useRecoveryGuardian.ts

Lines changed: 124 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ export const useRecoveryGuardian = () => {
9393
if (!contracts.guardianExecutor) throw new Error("GuardianExecutor contract address not configured");
9494
const client = getPublicClient({ chainId: defaultChain.id });
9595

96+
console.log(`[getGuardians] Fetching guardians for account: ${guardedAccount}`);
97+
9698
// Get list of guardian addresses
9799
const guardians = await client.readContract({
98100
address: contracts.guardianExecutor,
@@ -101,6 +103,8 @@ export const useRecoveryGuardian = () => {
101103
args: [guardedAccount],
102104
});
103105

106+
console.log(`[getGuardians] Found ${guardians.length} guardian(s):`, guardians);
107+
104108
// For each guardian, get their status to determine if active
105109
const guardiansWithStatus = await Promise.all(
106110
guardians.map(async (addr) => {
@@ -110,13 +114,16 @@ export const useRecoveryGuardian = () => {
110114
functionName: "guardianStatusFor",
111115
args: [guardedAccount, addr],
112116
});
117+
console.log(`[getGuardians] Guardian ${addr}: present=${isPresent}, active=${isActive}`);
113118
return { addr, isReady: isPresent && isActive };
114119
}),
115120
);
116121

117122
getGuardiansData.value = guardiansWithStatus;
123+
console.log("[getGuardians] Guardians with status:", guardiansWithStatus);
118124
return guardiansWithStatus;
119125
} catch (err) {
126+
console.error("[getGuardians] Error fetching guardians:", err);
120127
getGuardiansError.value = err as Error;
121128
return [];
122129
} finally {
@@ -166,7 +173,62 @@ export const useRecoveryGuardian = () => {
166173
const { inProgress: proposeGuardianInProgress, error: proposeGuardianError, execute: proposeGuardian } = useAsync(async (address: Address) => {
167174
if (!contracts.guardianExecutor) throw new Error("GuardianExecutor contract address not configured");
168175

169-
const client = getClient({ chainId: defaultChain.id });
176+
const client = getClient({ chainId: defaultChain.id, usePaymaster: true });
177+
const accountAddress = client.account.address;
178+
179+
console.log(`[proposeGuardian] Account: ${accountAddress}, Proposing guardian: ${address}`);
180+
181+
// Check if GuardianExecutor module is installed
182+
const publicClient = getPublicClient({ chainId: defaultChain.id });
183+
const isModuleInstalled = await publicClient.readContract({
184+
address: accountAddress,
185+
abi: [{
186+
type: "function",
187+
name: "isModuleInstalled",
188+
inputs: [
189+
{ name: "moduleTypeId", type: "uint256" },
190+
{ name: "module", type: "address" },
191+
{ name: "additionalContext", type: "bytes" },
192+
],
193+
outputs: [{ type: "bool" }],
194+
stateMutability: "view",
195+
}],
196+
functionName: "isModuleInstalled",
197+
args: [1n, contracts.guardianExecutor, "0x"], // 1 = MODULE_TYPE_EXECUTOR
198+
});
199+
200+
console.log(`[proposeGuardian] GuardianExecutor module installed: ${isModuleInstalled}`);
201+
202+
// Install module if not already installed
203+
if (!isModuleInstalled) {
204+
console.log("[proposeGuardian] Installing GuardianExecutor module...");
205+
const installTx = await client.writeContract({
206+
address: accountAddress,
207+
abi: [{
208+
type: "function",
209+
name: "installModule",
210+
inputs: [
211+
{ name: "moduleTypeId", type: "uint256" },
212+
{ name: "module", type: "address" },
213+
{ name: "initData", type: "bytes" },
214+
],
215+
outputs: [],
216+
stateMutability: "nonpayable",
217+
}],
218+
functionName: "installModule",
219+
args: [1n, contracts.guardianExecutor, "0x"], // 1 = MODULE_TYPE_EXECUTOR, empty initData
220+
});
221+
222+
console.log(`[proposeGuardian] Module installation transaction sent: ${installTx}`);
223+
const installReceipt = await client.waitForTransactionReceipt({ hash: installTx });
224+
225+
if (installReceipt.status === "reverted") {
226+
console.error("[proposeGuardian] Module installation reverted. Receipt:", installReceipt);
227+
throw new Error(`Failed to install GuardianExecutor module for account ${accountAddress}`);
228+
}
229+
230+
console.log("[proposeGuardian] GuardianExecutor module installed successfully");
231+
}
170232

171233
// Call proposeGuardian on the GuardianExecutor module through the smart account
172234
const tx = await client.writeContract({
@@ -176,8 +238,19 @@ export const useRecoveryGuardian = () => {
176238
args: [address],
177239
});
178240

241+
console.log(`[proposeGuardian] Transaction sent: ${tx}`);
242+
179243
// Wait for transaction receipt
180244
const receipt = await client.waitForTransactionReceipt({ hash: tx });
245+
246+
console.log(`[proposeGuardian] Transaction confirmed. Status: ${receipt.status}`);
247+
248+
if (receipt.status === "reverted") {
249+
console.error("[proposeGuardian] Transaction reverted. Receipt:", receipt);
250+
throw new Error(`Failed to propose guardian ${address} for account ${accountAddress}`);
251+
}
252+
253+
console.log(`[proposeGuardian] Guardian ${address} proposed successfully for account ${accountAddress}`);
181254
return receipt;
182255
});
183256

@@ -188,7 +261,7 @@ export const useRecoveryGuardian = () => {
188261
const { inProgress: removeGuardianInProgress, error: removeGuardianError, execute: removeGuardian } = useAsync(async (address: Address) => {
189262
if (!contracts.guardianExecutor) throw new Error("GuardianExecutor contract address not configured");
190263

191-
const client = getClient({ chainId: defaultChain.id });
264+
const client = getClient({ chainId: defaultChain.id, usePaymaster: true });
192265

193266
const tx = await client.writeContract({
194267
address: contracts.guardianExecutor,
@@ -206,28 +279,63 @@ export const useRecoveryGuardian = () => {
206279

207280
/**
208281
* Accept/confirm a guardian proposal for a given account
209-
* This is called by the guardian (from their EOA) to accept the guardian role
282+
* This is called by the guardian (from their EOA or smart account) to accept the guardian role
210283
*/
211284
const { inProgress: confirmGuardianInProgress, error: confirmGuardianError, execute: confirmGuardian } = useAsync(async <transport extends Transport, chain extends Chain, account extends Account>({ client, accountToGuard }: { client: WalletClient<transport, chain, account>; accountToGuard: Address }) => {
212285
if (!contracts.guardianExecutor) throw new Error("GuardianExecutor contract address not configured");
213286

214-
// Call acceptGuardian directly from the guardian's wallet
215-
const tx = await client.writeContract({
216-
address: contracts.guardianExecutor,
217-
abi: GuardianExecutorAbi,
218-
functionName: "acceptGuardian",
219-
args: [accountToGuard],
220-
chain: null,
221-
});
287+
const guardianAddress = client.account.address;
288+
console.log(`[confirmGuardian] Guardian address: ${guardianAddress}, Account to guard: ${accountToGuard}`);
289+
290+
// First, verify the guardian was actually proposed
291+
console.log("[confirmGuardian] Checking if guardian was proposed...");
292+
const guardians = await getGuardians(accountToGuard);
293+
const guardianStatus = guardians.find((g) => g.addr.toLowerCase() === guardianAddress.toLowerCase());
222294

223-
const transactionReceipt = await waitForTransactionReceipt(client, { hash: tx, confirmations: 1 });
295+
if (!guardianStatus) {
296+
throw new Error(`Guardian ${guardianAddress} was never proposed for account ${accountToGuard}. The account owner must propose this guardian first before you can accept.`);
297+
}
224298

225-
// Check if transaction was successful
226-
if (transactionReceipt.status === "reverted") {
227-
throw new Error("Transaction reverted: Guardian confirmation failed");
299+
if (guardianStatus.isReady) {
300+
console.log(`[confirmGuardian] Guardian ${guardianAddress} is already active for account ${accountToGuard}`);
301+
return { alreadyActive: true };
228302
}
229303

230-
return { transactionReceipt };
304+
console.log("[confirmGuardian] Guardian found in pending state, proceeding with acceptance...");
305+
306+
try {
307+
// Call acceptGuardian from the guardian's wallet
308+
const tx = await client.writeContract({
309+
address: contracts.guardianExecutor,
310+
abi: GuardianExecutorAbi,
311+
functionName: "acceptGuardian",
312+
args: [accountToGuard],
313+
chain: null,
314+
});
315+
316+
console.log(`[confirmGuardian] Transaction sent: ${tx}`);
317+
318+
const transactionReceipt = await waitForTransactionReceipt(client, { hash: tx, confirmations: 1 });
319+
320+
// Check if transaction was successful
321+
if (transactionReceipt.status === "reverted") {
322+
console.error("[confirmGuardian] Transaction reverted. Receipt:", transactionReceipt);
323+
throw new Error(`Transaction reverted: Guardian confirmation failed. Guardian: ${guardianAddress}, Account: ${accountToGuard}. This usually means the guardian was not properly proposed or the GuardianExecutor module is not installed.`);
324+
}
325+
326+
console.log("[confirmGuardian] Guardian confirmed successfully");
327+
return { transactionReceipt };
328+
} catch (error: unknown) {
329+
console.error("[confirmGuardian] Error details:", error);
330+
// Try to provide more specific error message
331+
if ((error as Error).message?.includes("GuardianNotFound")) {
332+
throw new Error(`Guardian ${guardianAddress} was not found in pending guardians for account ${accountToGuard}. Make sure the guardian was proposed first by the account owner.`);
333+
}
334+
if (error.message?.includes("NotInitialized")) {
335+
throw new Error(`GuardianExecutor module is not initialized for account ${accountToGuard}. The account owner needs to install the GuardianExecutor module first.`);
336+
}
337+
throw error;
338+
}
231339
});
232340

233341
/**

0 commit comments

Comments
 (0)