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>
)}
</>
)
}