LazorKit LogoLazorKit
Wallet Standard

Wallet Standard

Integrate LazorKit with @solana/wallet-adapter-react and other Wallet Standard clients.

LazorKit implements the Solana Wallet Standard, so you can drop it into any Wallet Adapter setup alongside Phantom, Solflare, Backpack, and friends.

When to use Wallet Standard

Pick this path if you already have a Wallet Adapter-powered app and want LazorKit to appear in the standard wallet picker. For a purpose-built integration, use the React SDK or React Native SDK directly — both expose session keys, deferred execution, and authority management that Wallet Standard doesn't cover.

Install

npm install @lazorkit/wallet \
  @solana/wallet-adapter-react @solana/wallet-adapter-react-ui

Setup

Register LazorKit as a standard wallet

Call registerLazorkitWallet once on the client. The Wallet Standard relies on the global window object, so mount registration inside an effect:

app/providers.tsx
'use client';

import { useEffect, useMemo } from 'react';
import {
  ConnectionProvider,
  WalletProvider,
} from '@solana/wallet-adapter-react';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { registerLazorkitWallet } from '@lazorkit/wallet';

const CONFIG = {
  RPC_URL: 'https://api.devnet.solana.com',
  PORTAL_URL: 'https://portal.lazor.sh',
  PAYMASTER: { paymasterUrl: 'https://kora.devnet.lazorkit.com' },
  CLUSTER: 'devnet' as const,
};

export function Providers({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    registerLazorkitWallet({
      rpcUrl: CONFIG.RPC_URL,
      portalUrl: CONFIG.PORTAL_URL,
      paymasterConfig: CONFIG.PAYMASTER,
      clusterSimulation: CONFIG.CLUSTER,
    });
  }, []);

  const wallets = useMemo(() => [/* other adapters here */], []);

  return (
    <ConnectionProvider endpoint={CONFIG.RPC_URL}>
      <WalletProvider wallets={wallets} autoConnect>
        <WalletModalProvider>{children}</WalletModalProvider>
      </WalletProvider>
    </ConnectionProvider>
  );
}

Use the standard hook anywhere

Once registered, LazorKit appears in the standard wallet picker. Consume it with the usual useWallet hook from @solana/wallet-adapter-react:

'use client';
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import {
  LAMPORTS_PER_SOL,
  PublicKey,
  SystemProgram,
  Transaction,
} from '@solana/web3.js';

export function TransferButton() {
  const { connection } = useConnection();
  const { publicKey, sendTransaction } = useWallet();

  const send = async () => {
    if (!publicKey) return;
    const tx = new Transaction().add(
      SystemProgram.transfer({
        fromPubkey: publicKey,                 // vault PDA — where funds live
        toPubkey: new PublicKey('RECIPIENT'),
        lamports: 0.01 * LAMPORTS_PER_SOL,
      }),
    );
    const sig = await sendTransaction(tx, connection);
    console.log('tx', sig);
  };

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

LazorKit handles the paymaster relay internally — you don't need to set feePayer or call signTransaction yourself.


Message signing (P256)

WebAuthn signs over derived WebAuthn payloads, not the raw challenge. signMessage returns a JSON-encoded { signature, signedPayload } pair so consumers can verify both pieces.

'use client';
import { useWallet } from '@solana/wallet-adapter-react';

export function SignAndVerify() {
  const { signMessage } = useWallet();

  const handle = async () => {
    if (!signMessage) return;

    const message = new TextEncoder().encode('Please verify ownership');
    const result = await signMessage(message);

    // result is a UTF-8 JSON blob — decode it:
    const { signature, signedPayload } = JSON.parse(new TextDecoder().decode(result));

    console.log('signature:', signature);
    console.log('signed payload:', signedPayload);
    // Verify with `crypto.subtle` against the wallet's P256 public key
  };

  return <button onClick={handle}>Sign Message</button>;
}

Why two fields?

The authenticator doesn't sign the challenge directly — it signs sha256(authenticatorData || sha256(clientDataJSON)). The verifier needs the exact bytes that were signed. signedPayload gives you that byte sequence; signature is the P256 signature over it.

See the React SDK's verifyMessage for an in-browser verifier that wraps crypto.subtle.


What Wallet Standard supports

FeatureAvailable
connect / disconnectyes
signTransaction / signAllTransactionsyes (via paymaster)
sendTransactionyes
signMessageyes (P256 — see above)
Session keysno — use React/RN SDK
Deferred executionno — use React/RN SDK
Authority managementno — use React/RN SDK

If you need advanced features, drop to the React SDK — it composes cleanly on top of the same wallet.


How it works

  1. RegisterregisterLazorkitWallet publishes the LazorKit adapter on window.
  2. Discover — Wallet Adapter detects the new standard wallet and lists it in the picker.
  3. Transact — When the user sends a transaction, LazorKit handles passkey signing and paymaster relay under the hood, then returns the confirmed signature.