Skip to content
Alchemy Logo

[NEW] On-chain Passkeys

This feature is in early access.

The WebAuthn Modular Account enables password-less authentication on-chain using passkeys (via WebAuthn), and is compatible with Alchemy’s Account Kit. This guide demonstrates how to register credentials, authenticate users, and send user operations using the @account-kit/smart-contracts package.

Instead of on-device verification it's on-chain verification. Devs are responsible for generating the credentials attached to those biometric webauthn compatible passkeys and then using our signer.

  • A frontend environment (React, Vite, Next.js, etc.)
  • Browser with WebAuthn and Credential Management API support
  • Install Alchemy Account Kit SDK smart contracts package (use the latest version - at least 4.52.1) and Viem
  yarn add @account-kit/[email protected] @account-kit/[email protected] viem

  1. User registers a WebAuthn credential
  2. Credential ID and public key are stored locally
  3. On login, navigator.credentials.get() fetches the credential
  4. The app creates a client using the credential
  5. A user operation is signed and sent

You are responsible for retaining your users’ public keys. Public keys generated by the WebAuthn specification are only retrievable once on the initial creation of the credential. As a precaution, we strongly suggest adding a secondary off-chain signer to use for account recovery

import { createWebAuthnCredential } from "viem/account-abstraction";
 
const credential = await createWebAuthnCredential({
  name: "Credential Name",
});
 
// store credential id to public key mapping
// NOTE: use of localStorage is for TESTING PURPOSES ONLY, NOT FOR PRODUCTION USE
localStorage.setItem(credential.id, credential.publicKey); //credentialIdAsBase64Encoded -> publicKeyHex

import { createModularAccountV2Client } from "@account-kit/smart-contracts";
import { alchemy, arbitrumSepolia } from "@account-kit/infra";
 
const publicKeyRequest: PublicKeyCredentialRequestOptions = {
  challenge: Uint8Array.fromHex("0x"), // Generate a random challenge
  rpId: "localhost", // should match your dApp domain
};
 
// retrieve available passkeys for the provided domain
const publicKeyCredential = await navigator.credentials.get({
  publicKeyRequest,
});
 
if (publicKeyCredential) {
  // verify that passkey with corresponding id exists on dApp
  const publicKeyHex = localStorage.getItem(publicKeyCredential.id);
  if (!publicKeyHex) throw new Error("Account does not exist");
 
  // create client to send transactions on behalf of verified user
  const accountClient = await createModularAccountV2Client({
    policyId: "YOUR_POLICY_ID",
    mode: "webauthn",
    credential: {
      id: publicKeyCredential.id,
      publicKey: publicKeyHex,
    },
    rpId: "localhost",
    chain: arbitrumSepolia,
    transport: alchemy({ apiKey: "YOUR_ALCHEMY_API_KEY" }),
  });
}
`createModularAccountV2Client` Parameters
ParameterTypeDescription
policyIdstringYour account policy UUID from the Alchemy dashboard
mode"webauthn"Specifies credential mode
credentialobject{ id: string, publicKey: Address }
rpIdstringRelying Party ID (e.g., localhost or your domain)
chainChainNetwork config (e.g., arbitrumSepolia)
transportTransportAlchemy or custom RPC transport

const operation = await accountClient.sendUserOperation({
  uo: {
    target: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", // Example: Vitalik's address
    data: "0x", // No calldata
    value: parseEther("0"),
  },
});

Get your React Native environment set up by following these docs. Once you’ve completed this setup, you can use the webauthn signer as detailed above!

localStorage is not available in React Native. Please use an alternative storage method.

This is only the initial SDK release for on-chain passkeys. We are actively working on the DevX so your feedback will be greatly appreciated. If you have any questions or are interested in learning more, please reach out!

Was this page helpful?