ZeroDev Tutorial -- Passkeys
In this tutorial, we will be building a Next.js app where your users can create smart accounts and send UserOps with passkeys.
Clone the template
We have prepared a Next.js template for you:
git clone git@github.com:zerodevapp/passkey-tutorial.git
If you ever want to check out the completed code, you can checkout the completed
branch. You can also see a deployed demo here.
Run the app in development mode:
npm i
npm run dev
Open app/page.tsx
. We will be working on this file for the rest of the tutorial.
Create a ZeroDev project
For this tutorial, create a ZeroDev project for Sepolia.
On app/page.tsx
, fill out these variables with the URLs found on your dashboard.
const BUNDLER_URL = ""
const PAYMASTER_URL = ""
const PASSKEY_SERVER_URL = ""
Create a smart account using passkeys
Let's hook up the Register
and Login
buttons so they actually do something.
In handleRegister
, add the following code:
// Function to be called when "Register" is clicked
const handleRegister = async () => {
setIsRegistering(true)
const webAuthnKey = await toWebAuthnKey({
passkeyName: username,
passkeyServerUrl: PASSKEY_SERVER_URL,
mode: WebAuthnMode.Register,
passkeyServerHeaders: {}
})
const passkeyValidator = await toPasskeyValidator(publicClient, {
webAuthnKey,
entryPoint,
kernelVersion: KERNEL_V3_1,
validatorContractVersion: PasskeyValidatorContractVersion.V0_0_2
})
await createAccountAndClient(passkeyValidator)
setIsRegistering(false)
window.alert('Register done. Try sending UserOps.')
}
And in handleLogin
, add the following code:
const handleLogin = async () => {
setIsLoggingIn(true)
const webAuthnKey = await toWebAuthnKey({
passkeyName: username,
passkeyServerUrl: PASSKEY_SERVER_URL,
mode: WebAuthnMode.Login,
passkeyServerHeaders: {}
})
const passkeyValidator = await toPasskeyValidator(publicClient, {
webAuthnKey,
entryPoint,
kernelVersion: KERNEL_V3_1,
validatorContractVersion: PasskeyValidatorContractVersion.V0_0_2
})
await createAccountAndClient(passkeyValidator)
setIsLoggingIn(false)
window.alert('Login done. Try sending UserOps.')
}
In this tutorial, we are using a public passkey server URL. In practice, you'd create your own passkey server URL from the dashboard.
Now modify createAccountAndClient
to actually create the account using the passkeyValidator
:
const createAccountAndClient = async (passkeyValidator: any) => {
kernelAccount = await createKernelAccount(publicClient, {
plugins: {
sudo: passkeyValidator,
},
entryPoint,
kernelVersion: KERNEL_V3_1
})
kernelClient = createKernelAccountClient({
account: kernelAccount,
chain: CHAIN,
bundlerTransport: http(BUNDLER_URL),
client: publicClient,
paymaster: {
getPaymasterData: (userOperation) => {
const zerodevPaymaster = createZeroDevPaymasterClient({
chain: CHAIN,
transport: http(PAYMASTER_URL),
})
return zerodevPaymaster.sponsorUserOperation({
userOperation,
})
}
},
userOperation: {
estimateFeesPerGas: async ({bundlerClient}) => {
return getUserOperationGasPrice(bundlerClient)
}
}
})
setIsKernelClientReady(true)
setAccountAddress(kernelAccount.address)
}
At this point, you should be able to create passkey accounts with either Register
or Login
.
Sending UserOps
Sending UserOps from a passkey account is the same as sending them from any account. Modify handleSendUserOp
as such:
const handleSendUserOp = async () => {
setIsSendingUserOp(true)
setUserOpStatus('Sending UserOp...')
const userOpHash = await kernelClient.sendUserOperation({
callData: await kernelAccount.encodeCalls([{
to: contractAddress,
value: BigInt(0),
data: encodeFunctionData({
abi: contractABI,
functionName: "mint",
args: [kernelAccount.address],
}),
}]),
})
setUserOpHash(userOpHash)
await kernelClient.waitForUserOperationReceipt({
hash: userOpHash,
})
// Update the message based on the count of UserOps
const userOpMessage = `UserOp completed. <a href="https://jiffyscan.xyz/userOpHash/${userOpHash}?network=sepolia" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:text-blue-700">Click here to view.</a>`
setUserOpStatus(userOpMessage)
setIsSendingUserOp(false)
}
Now try sending some UserOps!
Also, the UserOps are sponsored thanks to paymasters -- that's why you are able to send UserOps from an account with no ETH.
Next Steps
In this tutorial, you were able to create smart accounts and send UserOps with passkeys.
For next steps:
- Check out the core API to learn more about the SDK
- Learn more about passkeys
- Read some code examples of using ZeroDev