Skip to content

How to Integrate Pimlico with Passkey (WebAuthn) signer

This how-to guide will walk you through the steps to integrate Passkey (WebAuthn) signer with a smart account whose user operations are relayed and sponsored by Pimlico.

Install the required packages

npm install viem permissionless

Create credentials

import { useState } from "react"
import {
    createWebAuthnCredential,
} from "viem/account-abstraction"
 
export function PasskeysDemo() {
    const [credential, setCredential] = useState<P256Credential>(() =>
        JSON.parse(localStorage.getItem("credential"))
    )
 
    const createCredential = async () => {
        const credential = await createWebAuthnCredential({
            name: "Wallet"
        })
        localStorage.setItem("credential", JSON.stringify(credential))
        setCredential(credential)
    }
 
    if (!credential)
        return (
            <button type="button" onClick={createCredential}>
                Create credential
            </button>
        )
 
    return (
        <div>
            <p>Credential: {credential.id}</p>
        </div>
    )
}

Create Kernel Smart Account

import {
    type SmartAccountClient,
    createSmartAccountClient
} from "permissionless"
import {
    type ToKernelSmartAccountReturnType,
    toKernelSmartAccount
} from "permissionless/accounts"
import {
    entryPoint07Address,
    toWebAuthnAccount
} from "viem/account-abstraction"
 
const pimlicoUrl = `https://api.pimlico.io/v2/${chain.id}/rpc?apikey=${pimlicoApiKey}`
const pimlicoClient = createPimlicoClient({ 
    chain, 
    transport: http(pimlicoUrl) 
}) 
 
export function PasskeysDemo() {
    const [smartAccountClient, setSmartAccountClient] =
        React.useState<
            SmartAccountClient<
                Transport,
                Chain,
                ToKernelSmartAccountReturnType<"0.7">
            >
        >()
    ...
    React.useEffect(() => { 
        if (!credential) return
        toKernelSmartAccount({ 
            client: publicClient, 
            version: "0.3.1", 
            owners: [toWebAuthnAccount({ credential })], 
            entryPoint: { 
                address: entryPoint07Address, 
                version: "0.7"
            } 
        }).then((account: ToKernelSmartAccountReturnType<"0.7">) => { 
            setSmartAccountClient( 
                createSmartAccountClient({ 
                    account, 
                    paymaster: pimlicoClient, 
                    chain, 
                    userOperation: { 
                        estimateFeesPerGas: async () =>
                            (await pimlicoClient.getUserOperationGasPrice()) 
                                .fast 
                    }, 
                    bundlerTransport: http(pimlicoUrl) 
                }) 
            ) 
        }) 
    }, [credential]) 
    ...
}

Send a transaction

export function PasskeysDemo() {
    ...
    const [txHash, setTxHash] = React.useState<Hex>() 
 
    const sendUserOperation = async ( 
        event: React.FormEvent<HTMLFormElement>
    ) => { 
        event.preventDefault() 
        if (!smartAccountClient) return
 
        const formData = new FormData(event.currentTarget) 
        const to = formData.get("to") as `0x${string}`
        const value = formData.get("value") as string
 
        const txHash = await smartAccountClient.sendTransaction({ 
            calls: [ 
                { 
                    to, 
                    value: parseEther(value) 
                } 
            ], 
        }) 
        setTxHash(txHash) 
    } 
 
    return ( 
        <>
            <h2>Account</h2>
            <p>Address: {smartAccountClient?.account?.address}</p>
 
            <h2>Send User Operation</h2>
            <form onSubmit={sendUserOperation}>
                <input name="to" placeholder="Address" />
                <input name="value" placeholder="Amount (ETH)" />
                <button type="submit">Send</button>
                {txHash && <p>Transaction Hash: {txHash}</p>}
            </form>
        </> 
    ) 
}

Sign & verify a message

export function PasskeysDemo() {
    const [signature, setSignature] = React.useState<Hex>() 
    const [isVerified, setIsVerified] = React.useState<boolean>() 
    ...
    const signAndVerifyMessage = async () => { 
        if (!smartAccountClient) return
        const signature = await smartAccountClient.signTypedData(typedData) 
 
        const isVerified = await publicClient.verifyTypedData({ 
            ...typedData, 
            address: smartAccountClient.account.address, 
            signature 
        }) 
        setIsVerified(isVerified) 
        setSignature(signature) 
    } 
    return ( 
        <>
            <h2>Account</h2>
            <p>Address: {smartAccountClient?.account?.address}</p>
 
            <h2>Sign typed data</h2>
            <button type="button" onClick={signAndVerifyMessage}>
                Sign typed data Test
            </button>
            {signature && ( 
                <p>
                    Signature: <pre>{signature}</pre>
                </p> 
            )}
            {isVerified !== undefined && ( 
                <p>Verified: {isVerified.toString()}</p> 
            )}
        </> 
    ) 
}