LazorKit LogoLazorKit
React SDK

Getting Started

Ship a working LazorKit integration in under 10 minutes.

Three files total: a provider, a connect button, and a transaction handler. All three use the same useWallet hook.

Zero-config defaults

All provider props are optional. With nothing set, the SDK targets Solana Devnet through portal.lazor.sh and the public Kora paymaster — perfect for demos and local dev.

Prerequisites

  • React 18+
  • Browser with WebAuthn support (all modern browsers)
  • Client-side context — passkey APIs aren't available during SSR

Install

Install the SDK and peer

npm install @lazorkit/wallet @solana/web3.js

@solana/web3.js is a peer dependency. Install it explicitly so your bundler resolves a single version.

Add a Buffer polyfill

Solana libraries assume Node's Buffer global. Bundler-specific setup:

vite.config.ts
import { nodePolyfills } from 'vite-plugin-node-polyfills';

export default defineConfig({
  plugins: [nodePolyfills()],
});

Mark any file that uses wallet logic with "use client". For the Buffer global, add a one-time shim in your providers file:

app/providers.tsx
'use client';

import { Buffer } from 'buffer';
if (typeof window !== 'undefined') {
  (window as any).Buffer ??= Buffer;
}

CRA handles Buffer automatically for most versions. If you hit errors, install buffer and add:

src/polyfills.ts
import { Buffer } from 'buffer';
(window as any).Buffer = (window as any).Buffer ?? Buffer;

Import ./polyfills before anything else in src/index.tsx.

Mount the provider

app/providers.tsx
'use client';

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

export function Providers({ children }: { children: React.ReactNode }) {
  return <LazorkitProvider>{children}</LazorkitProvider>;
}

That's the minimum. Override defaults via props:

<LazorkitProvider
  rpcUrl={process.env.NEXT_PUBLIC_RPC_URL}
  portalUrl="https://portal.lazor.sh"
  paymasterConfig={{
    paymasterUrl: process.env.NEXT_PUBLIC_PAYMASTER_URL!,
    apiKey: process.env.NEXT_PUBLIC_PAYMASTER_KEY,
  }}
>
  {children}
</LazorkitProvider>

First flow: connect + send

Connect button

components/ConnectButton.tsx
'use client';
import { useWallet } from '@lazorkit/wallet';

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

  if (isConnected && wallet) {
    const vault = wallet.vaultPda ?? wallet.smartWallet;
    return (
      <button onClick={() => disconnect()}>
        Disconnect ({vault.slice(0, 6)}…)
      </button>
    );
  }
  return (
    <button onClick={() => connect()} disabled={isConnecting}>
      {isConnecting ? 'Connecting…' : 'Connect Wallet'}
    </button>
  );
}

vaultPda is the user-facing address

wallet.vaultPda is the SOL-holding account — show this to users and use it as fromPubkey for transfers. wallet.smartWallet (and smartWalletPubkey) is the internal Wallet PDA used by the program for authority resolution, not where funds live. On older cached WalletInfo records without vaultPda, derive it with findVaultPda(new PublicKey(wallet.smartWallet))[0].

Transfer SOL

components/TransferButton.tsx
'use client';
import { useWallet, findVaultPda } from '@lazorkit/wallet';
import { LAMPORTS_PER_SOL, PublicKey, SystemProgram } from '@solana/web3.js';

export function TransferButton() {
  const { wallet, signAndSendTransaction } = useWallet();

  const send = async () => {
    if (!wallet) return;
    // vaultPda is the SOL-holding address — use it as the transfer source.
    const vault = wallet.vaultPda
      ? new PublicKey(wallet.vaultPda)
      : findVaultPda(new PublicKey(wallet.smartWallet))[0];

    const ix = SystemProgram.transfer({
      fromPubkey: vault,
      toPubkey: new PublicKey('RECIPIENT_ADDRESS'),
      lamports: 0.01 * LAMPORTS_PER_SOL,
    });
    const sig = await signAndSendTransaction({ instructions: [ix] });
    console.log('tx', sig);
  };

  return <button onClick={send}>Send 0.01 SOL</button>;
}

(Optional) Display balance

'use client';
import { useEffect, useMemo, useState } from 'react';
import { Connection, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
import { useWallet, findVaultPda } from '@lazorkit/wallet';

export function Balance() {
  const { wallet } = useWallet();
  const vault = useMemo(() => {
    if (!wallet) return null;
    return wallet.vaultPda
      ? new PublicKey(wallet.vaultPda)
      : findVaultPda(new PublicKey(wallet.smartWallet))[0];
  }, [wallet?.smartWallet, wallet?.vaultPda]);

  const [sol, setSol] = useState(0);

  useEffect(() => {
    if (!vault) return;
    const connection = new Connection(process.env.NEXT_PUBLIC_RPC_URL!);
    connection.getBalance(vault).then((l) => setSol(l / LAMPORTS_PER_SOL));
  }, [vault?.toBase58()]);

  return <span>{sol.toFixed(4)} SOL</span>;
}

Next level