|
36 | 36 | <script setup lang="ts"> |
37 | 37 | import { useAppKitAccount } from "@reown/appkit/vue"; |
38 | 38 | import { type Address, bytesToBigInt, type Hex, pad } from "viem"; |
| 39 | +import { waitForTransactionReceipt } from "viem/actions"; |
39 | 40 | import { sendTransaction } from "viem/zksync"; |
| 41 | +import { OidcRecoveryValidatorAbi } from "zksync-sso/abi"; |
40 | 42 | import { createNonceV2 } from "zksync-sso-circuits"; |
41 | 43 |
|
42 | 44 | import { GOOGLE_CERTS_URL } from "./constants"; |
43 | 45 |
|
44 | | -const { getWalletClient, defaultChain, getOidcClient } = useClientStore(); |
| 46 | +const { getWalletClient, getPublicClient, defaultChain, getOidcClient } = useClientStore(); |
45 | 47 | const { startGoogleOauth } = useGoogleOauth(); |
46 | 48 | const accountData = useAppKitAccount(); |
47 | 49 | const { |
@@ -92,6 +94,7 @@ function buildBlindingFactor(): bigint { |
92 | 94 |
|
93 | 95 | async function go() { |
94 | 96 | const client = await getWalletClient({ chainId: defaultChain.id }); |
| 97 | + const publicClient = getPublicClient({ chainId: defaultChain.id }); |
95 | 98 | const blindingFactor = buildBlindingFactor(); |
96 | 99 | const oidcData = await getOidcAccounts(userAddress.value); |
97 | 100 | if (oidcData === undefined) { |
@@ -150,22 +153,63 @@ async function go() { |
150 | 153 | timeLimit, |
151 | 154 | ); |
152 | 155 |
|
| 156 | + // Preflight checks |
| 157 | + const recoveryAddress = contractsByChain[defaultChain.id].recoveryOidc as Address; |
| 158 | + const senderAddress = client.account.address as Address; |
| 159 | +
|
| 160 | + // Ensure OIDC validator is initialized and wired correctly |
| 161 | + const [webAuthValidatorAddr, keyRegistryAddr, verifierAddr] = await Promise.all([ |
| 162 | + publicClient.readContract({ address: recoveryAddress, abi: OidcRecoveryValidatorAbi, functionName: "webAuthValidator", args: [] }), |
| 163 | + publicClient.readContract({ address: recoveryAddress, abi: OidcRecoveryValidatorAbi, functionName: "keyRegistry", args: [] }), |
| 164 | + publicClient.readContract({ address: recoveryAddress, abi: OidcRecoveryValidatorAbi, functionName: "verifier", args: [] }), |
| 165 | + ]) as [Address, Address, Address]; |
| 166 | +
|
| 167 | + if ( |
| 168 | + webAuthValidatorAddr === "0x0000000000000000000000000000000000000000" |
| 169 | + || keyRegistryAddr === "0x0000000000000000000000000000000000000000" |
| 170 | + || verifierAddr === "0x0000000000000000000000000000000000000000" |
| 171 | + ) { |
| 172 | + throw new Error(`OIDC recovery validator at ${recoveryAddress} is not initialized`); |
| 173 | + } |
| 174 | +
|
| 175 | + const expectedPasskey = contractsByChain[defaultChain.id].passkey as Address | undefined; |
| 176 | + if (expectedPasskey && webAuthValidatorAddr.toLowerCase() !== expectedPasskey.toLowerCase()) { |
| 177 | + throw new Error(`webAuthValidator mismatch: on-chain=${webAuthValidatorAddr}, expected=${expectedPasskey}`); |
| 178 | + } |
| 179 | +
|
| 180 | + // Ensure sender has some balance for gas |
| 181 | + const balance = await publicClient.getBalance({ address: senderAddress }); |
| 182 | + if (balance === 0n) { |
| 183 | + throw new Error("Insufficient balance to pay gas for recovery transaction"); |
| 184 | + } |
| 185 | +
|
153 | 186 | const sendTransactionArgs = { |
154 | 187 | account: client.account, |
155 | 188 | to: contractsByChain[defaultChain.id].recoveryOidc, |
156 | 189 | data: calldata, |
157 | | - gas: 20_000_000, |
| 190 | + value: 0n, |
| 191 | + gas: 20_000_000n, |
158 | 192 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
159 | 193 | } as any; |
160 | | - await sendTransaction(client, sendTransactionArgs); |
161 | 194 |
|
| 195 | + const sentTx = await sendTransaction(client, sendTransactionArgs); |
| 196 | +
|
| 197 | + const startRecoveryReceipt = await waitForTransactionReceipt(client, { hash: sentTx, confirmations: 1 }); |
| 198 | + if (startRecoveryReceipt.status !== "success") { |
| 199 | + throw new Error(`Recovery transaction ${startRecoveryReceipt.status}`); |
| 200 | + } |
162 | 201 | const oidcClient = getOidcClient({ chainId: defaultChain.id, address: userAddress.value }); |
163 | 202 |
|
164 | | - await oidcClient.addNewPasskeyViaOidc({ |
| 203 | + const addedPasskey = await oidcClient.addNewPasskeyViaOidc({ |
165 | 204 | credentialId: passkey.value.credentialId, |
166 | 205 | passkeyPubKey: passkey.value.passkeyPubKey, |
167 | 206 | passkeyDomain: window.location.origin, |
168 | 207 | }); |
| 208 | +
|
| 209 | + if (addedPasskey.status !== "success") { |
| 210 | + throw new Error(`Failed to add passkey via OIDC: ${addedPasskey.status}`); |
| 211 | + } |
| 212 | +
|
169 | 213 | recoverySuccessful.value = true; |
170 | 214 | } |
171 | 215 | </script> |
0 commit comments