Building the Frontend
Frontend Setup
We will use Next.js to build the frontend.
Move out of your contract
folder and back into the zksync-webauthn
folder.
Make a new Next.js project for the frontend using the configuration below:
npx create-next-app@latest
✔ What is your project named? … frontend
✔ Would you like to use TypeScript? Yes
✔ Would you like to use ESLint? Yes
✔ Would you like to use Tailwind CSS? No
✔ Would you like to use `src/` directory? Yes
✔ Would you like to use App Router? (recommended) No
✔ Would you like to customize the default import alias (@/*)? No
Move into your projects and install the dependencies below:
cd frontend
npm install @simplewebauthn/browser@10.0.0 @simplewebauthn/server@10.0.1 cbor@9.0.2 ethers@5.7.2 zksync-ethers@5.1.0
Environment Variables
Make a new file called .env.local
, copy the variables below,
and add the deployed contract addresses from the previous section.
NEXT_PUBLIC_AA_FACTORY_ADDRESS=
NEXT_PUBLIC_NFT_CONTRACT_ADDRESS=
NEXT_PUBLIC_PAYMASTER_ADDRESS=
Home Page
We can start by replacing the home page with one that has four links for each page we will make.
Replace your frontend/src/pages/index.tsx
file with the code below.
Layout
Next, let's create the Layout
component used in the home page.
Create a src/components
folder and a new file inside called Layout.tsx
.
mkdir src/components
touch src/components/Layout.tsx
Copy the Layout
component below to your file:
Styles
Let's also update the styles in src/styles/globals.css
.
API
Let's setup our API endpoints next. We need to do this because some parts of the WebAuthn ceremonies must run on a server instead of the client.
Get Registration Options
Create a new file in the src/pages/api
folder called get-registration-options.ts
.
touch src/pages/api/get-registration-options.ts
The get-registration-options
endpoint is used to generate the options input object for the WebAuthn registration ceremony.
These options tell the browser how to configure the registration, including setting the name of the passkey, the domain of the application making the request, what signature types are allowed, and whether user verification is required.
It's important here that we are only allowing -7
as the only
supported algorithm ID,
as this represents the same signature scheme that is supported by
the P256Verify
precompile contract we are calling in the smart account validation.
You can learn more about the options available for registration in the simplewebauthn
docs.
Get Authentication Options
Create another file in the src/pages/api
folder called get-authentication-options.ts
.
touch src/pages/api/get-authentication-options.ts
The get-authentication-options
endpoint is used to generate the options input object for the WebAuthn authentication ceremony.
You can learn more about the options available for authentication in the simplewebauthn
docs.
The challenge passed in through the request body represents the transaction data that would normally be signed by a wallet like Metamask.
The generateAuthenticationOptions
method converts the challenge to a base64 URL for the options input object.
If you want to verify that the final base64URL challenge in the options is indeed equal to the input challenge,
you can use the isoBase64URL
method provided by @simplewebauthn
.
import { isoBase64URL } from '@simplewebauthn/server/helpers';
const decodedChallenge = isoBase64URL.toUTF8String(options.challenge);
Utils
Next, let's make a new folder called utils
where we can add some utility functions.
mkdir utils
String Conversions
Create a new file inside the utils
folder called string.ts
to help handle some string conversions we will need to work with hex values and base64 URLs.
touch utils/string.ts
Transaction Utils
Next, create a file called tx.ts
.
touch utils/tx.ts
zksync-ethers/build/utils
to zksync-ethers/build/src/utils
.This file contains helper functions to get data for a transaction and register a new passkey in a smart account.
- The
getTransaction
function constructs a transaction that is sponsored by the paymaster contract using thegetPaymasterOverrides
function. - The
registerKeyInAccount
function sends a transaction to register a new passkey as ther1Owner
of a smart account. TheupdateR1Owner
function must be called by the smart contract account itself. Because there is no passkey registered yet, the original wallet that created the account must sign the transaction, which we can pass as a custom signature. This means that wallet passed into this function must be the same wallet created in the "Create Account" page.
WebAuthn Utils
The last file we need to add in the utils
folder is called webauthn.ts
.
touch utils/webauthn.ts
This file contains helper functions to start the WebAuthn authentication process and send transactions with a WebAuthn signature.
- The
authenticate
function gets the authentication options from the endpoint we set up earlier, and passes them into thestartAuthentication
method provided by@simplewebauthn
. - The
signAndSend
function takes the response from the authentication process and processes it so it's in the correct format for the smart account contract. Remember that the WebAuthn authentication process returns three pieces of information that we need for validation: the WebAuthn signature, theauthenticatorData
, and theclientData
. ThegetSignatureFromAuthResponse
function encodes thers
values from the signature along with theauthenticatorData
and theclientData
into one signature that can be passed into the smart contract via the transactioncustomData.customSignature
. - The
getDataToSign
function generates a signed digest of a transaction. - The
getRS
function extracts thers
values from the WebAuthn signature and normalizes the values to ensure they are in the lower half of the secp256r1 curve. This is required to pass the malleability check in the smart account contract. - The
getPublicKeyFromAuthenticatorData
function extracts the public key from theauthenticatorData
returned from the registration process.
Let's complete the frontend in the next section.