DEV Community

Cover image for Solana Passkeys on the Web (No Extension Required)
Fred
Fred

Posted on

Solana Passkeys on the Web (No Extension Required)

Passkeys are the most important UX upgrade for wallets since mobile. Instead of seed phrases and browser extensions, users authenticate with the same biometrics they already use every day: Face ID, Touch ID, or Windows Hello.

This post walks through a minimal Vite + React demo that uses the LazorKit SDK to:

  • Create a passkey-based smart wallet
  • Display the wallet address
  • Send a standard SOL transfer on devnet

This is an example demo, not a full product. LazorKit is pre-audit—don’t use it in production.


Why passkeys for Solana

Traditional wallets require users to:

  • Install a third‑party extension or app
  • Save (and never lose) a seed phrase
  • Jump through popups and approvals

Passkeys flip this flow:

  • The private key stays in secure hardware (device enclave)
  • Users authenticate with biometrics
  • The browser handles the UX

That’s the closest we have to “Web2 login” on-chain.


What we’re building

A single-page demo with three sections:

  1. Passkey connect (create or restore)
  2. Airdrop (optional devnet funding)
  3. SOL transfer (standard tx, signed by passkey)

Everything is intentionally small and readable.


Core idea: wrap your app in LazorKit

The provider handles the passkey flow and smart wallet management.

import { LazorkitProvider } from '@lazorkit/wallet';

export default function App() {
  return (
    <LazorkitProvider
      rpcUrl="https://api.devnet.solana.com"
      portalUrl="https://portal.lazor.sh"
    >
      <YourApp />
    </LazorkitProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

From here, you can use the useWallet hook anywhere.


Passkey connect flow

import { useWallet } from '@lazorkit/wallet';

export function ConnectButton() {
  const { connect, disconnect, isConnected, wallet } = useWallet();

  if (isConnected && wallet) {
    return <button onClick={() => disconnect()}>Disconnect</button>;
  }

  return <button onClick={() => connect({ feeMode: 'paymaster' })}>Connect</button>;
}
Enter fullscreen mode Exit fullscreen mode

When connect() runs, the browser prompts for passkey creation or authentication. No seed phrase, no extension.


Sending SOL (standard transaction)

import { useWallet } from '@lazorkit/wallet';
import { SystemProgram, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';

const { signAndSendTransaction, smartWalletPubkey } = useWallet();

const destination = new PublicKey('RECIPIENT_ADDRESS');
const instruction = SystemProgram.transfer({
  fromPubkey: smartWalletPubkey,
  toPubkey: destination,
  lamports: 0.01 * LAMPORTS_PER_SOL
});

const signature = await signAndSendTransaction({
  instructions: [instruction]
});
Enter fullscreen mode Exit fullscreen mode

This is a regular devnet transfer. It requires SOL for fees. (Paymaster support is optional and not included in this demo.)


What the demo includes

  • Passkey connect + disconnect
  • Smart wallet address display
  • Copy‑to‑clipboard for wallet address
  • Recipient input with .env default
  • Optional devnet airdrop button
  • Readable docs + tutorials

How to run it

npm install
cp .env.example .env
npm run dev
Enter fullscreen mode Exit fullscreen mode

Then open the Vite URL and click Connect Passkey Wallet.


Repo structure (high‑level)

src/
  components/
    ConnectButton.jsx
    AirdropButton.jsx
    TransferButton.jsx
    StatusPanel.jsx
  lib/config.js
  App.jsx
  main.jsx
Enter fullscreen mode Exit fullscreen mode

What’s next

This demo is intentionally minimal. Some easy next steps:

  • Add SPL token transfers (USDC)
  • Integrate a swap aggregator (e.g., Jupiter)
  • Add session management or device recovery UX

References


If you’re building Solana UX in 2026, passkeys aren’t optional anymore; they’re the baseline.

Top comments (0)