Say goodbye to seed phrases. Say goodbye to gas fees.
This starter kit demonstrates how to build production-ready Solana dApps using Lazorkit SDK — enabling biometric authentication (FaceID, TouchID, Windows Hello) and gasless transactions out of the box.
📖 Documentation · 🎮 Live Demo · 🐛 Report Bug
Building a Web3 app shouldn't feel like building a spaceship. Lazor Turbo Starter removes the hardest parts:
| Traditional Wallet UX | With Lazorkit |
|---|---|
| ❌ Users must install browser extensions | ✅ Works instantly on any device |
| ❌ Seed phrase management is scary | ✅ Biometric auth they already know |
| ❌ Users need SOL for gas fees | ✅ Gasless via Paymaster — zero SOL needed |
| ❌ Complex Web3 onboarding | ✅ One-tap connection |
- Passkey Authentication — Replace seed phrases with FaceID/TouchID
- Gasless Transactions — Let users transact without owning SOL
- Clean Architecture — Production-ready Next.js 14 patterns
- Type Safety — Strict TypeScript throughout
lazor-turbo-starter/
├── 📄 package.json # Dependencies & scripts
├── 📄 tailwind.config.ts # Custom theme & animations
├── 📄 tsconfig.json # Strict TypeScript config
├── 📄 next.config.js # Next.js + Web3 optimizations
├── 📄 .env.example # Environment variables template
│
├── 📁 src/
│ ├── 📁 app/
│ │ ├── 📄 layout.tsx # Root layout with providers
│ │ ├── 📄 page.tsx # Landing page UI
│ │ └── 📄 globals.css # Global styles + animations
│ │
│ ├── 📁 components/
│ │ └── 📄 LazorProvider.tsx # SDK initialization wrapper
│ │
│ └── 📁 hooks/
│ └── 📄 useGaslessTip.ts # Custom hook for gasless txs
│
└── 📄 README.md # You are here! 👋
Get up and running in under 2 minutes:
- Node.js 18+ installed
- pnpm, npm, or yarn
# Clone the repository
git clone https://github.com/your-org/lazor-turbo-starter.git
# Navigate to the project
cd lazor-turbo-starter
# Install dependencies
npm install
# Copy environment variables
cp .env.example .env.local
# Start the development server
npm run devOpen http://localhost:3000 to see your app! 🎉
Passkeys use the WebAuthn standard to authenticate users using their device's biometrics (FaceID, TouchID, Windows Hello) or a hardware security key.
sequenceDiagram
participant User
participant Browser
participant Lazorkit
participant Solana
User->>Browser: Click "Connect with FaceID"
Browser->>User: Biometric prompt (FaceID/TouchID)
User->>Browser: Authenticate
Browser->>Lazorkit: WebAuthn credential
Lazorkit->>Lazorkit: Derive Solana keypair
Lazorkit->>Solana: Register wallet
Lazorkit->>Browser: Return wallet address
Browser->>User: "Connected as 8abc...xyz"
Create src/components/LazorProvider.tsx:
'use client';
import { LazorkitProvider } from '@lazorkit/react-sdk';
import { ReactNode } from 'react';
const config = {
rpcUrl: process.env.NEXT_PUBLIC_SOLANA_RPC_URL || 'https://api.devnet.solana.com',
paymasterUrl: process.env.NEXT_PUBLIC_PAYMASTER_URL || 'https://api.devnet.lazorkit.com/paymaster',
appName: 'My dApp',
};
export function LazorProvider({ children }: { children: ReactNode }) {
return (
<LazorkitProvider
rpcUrl={config.rpcUrl}
paymasterUrl={config.paymasterUrl}
appName={config.appName}
>
{children}
</LazorkitProvider>
);
}In src/app/layout.tsx:
import { LazorProvider } from '@/components/LazorProvider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<LazorProvider>
{children}
</LazorProvider>
</body>
</html>
);
}In any client component:
'use client';
import { useLazorWallet } from '@lazorkit/react-sdk';
export default function ConnectButton() {
const { loginWithPasskey, address, isConnected } = useLazorWallet();
if (isConnected) {
return <p>Connected: {address}</p>;
}
return (
<button onClick={() => loginWithPasskey()}>
Connect with FaceID
</button>
);
}Tip
The address is a Solana public key derived from the user's passkey. It's unique per app + device combination.
The Paymaster is a service that sponsors transaction fees on behalf of your users. This means:
- Users don't need SOL to transact
- Better onboarding for new crypto users
- You control who gets free transactions
sequenceDiagram
participant User
participant dApp
participant Lazorkit
participant Paymaster
participant Solana
User->>dApp: Click "Send Tip"
dApp->>Lazorkit: Build transaction
Lazorkit->>Paymaster: Request sponsorship
Paymaster->>Paymaster: Validate request
Paymaster->>Lazorkit: Add fee signature
Lazorkit->>User: Biometric prompt
User->>Lazorkit: Authenticate
Lazorkit->>Solana: Submit transaction
Solana->>dApp: Transaction confirmed
dApp->>User: "Tip sent! 🎉"
Create src/hooks/useGaslessTip.ts:
'use client';
import { useCallback, useState } from 'react';
import { useLazorWallet } from '@lazorkit/react-sdk';
import { PublicKey, Transaction } from '@solana/web3.js';
import { createTransferInstruction, getAssociatedTokenAddressSync } from '@solana/spl-token';
// USDC Mint on Devnet
const USDC_MINT = new PublicKey('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU');
const USDC_DECIMALS = 6;
export function useGaslessTip() {
const [isLoading, setIsLoading] = useState(false);
const { address, signAndSendTransaction } = useLazorWallet();
const sendTip = useCallback(async (recipient: string, amount: number) => {
if (!address) throw new Error('Not connected');
setIsLoading(true);
try {
const senderPubkey = new PublicKey(address);
const recipientPubkey = new PublicKey(recipient);
// Get Associated Token Addresses
const senderATA = getAssociatedTokenAddressSync(USDC_MINT, senderPubkey);
const recipientATA = getAssociatedTokenAddressSync(USDC_MINT, recipientPubkey);
// Build transfer instruction
const instruction = createTransferInstruction(
senderATA,
recipientATA,
senderPubkey,
BigInt(amount * 10 ** USDC_DECIMALS)
);
const tx = new Transaction().add(instruction);
// ✨ THE MAGIC: paysFee: false triggers Paymaster
const signature = await signAndSendTransaction(tx, { paysFee: false });
return signature;
} finally {
setIsLoading(false);
}
}, [address, signAndSendTransaction]);
return { sendTip, isLoading };
}'use client';
import { useGaslessTip } from '@/hooks/useGaslessTip';
export default function TipCard() {
const { sendTip, isLoading } = useGaslessTip();
const handleTip = async () => {
try {
const signature = await sendTip('RECIPIENT_ADDRESS_HERE', 1);
console.log('Success!', signature);
} catch (err) {
console.error('Failed:', err);
}
};
return (
<button onClick={handleTip} disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send $1 USDC (Gasless!)'}
</button>
);
}Important
The paysFee: false flag is what makes the transaction gasless. Without it, the user would need SOL for fees.
Create a .env.local file in your project root:
# Solana RPC URL
NEXT_PUBLIC_SOLANA_RPC_URL=https://api.devnet.solana.com
# Lazorkit Paymaster URL
NEXT_PUBLIC_PAYMASTER_URL=https://api.devnet.lazorkit.com/paymaster
# Your app name (shown in passkey prompts)
NEXT_PUBLIC_APP_NAME=My Awesome dApp| Network | RPC URL |
|---|---|
| Devnet | https://api.devnet.solana.com |
| Mainnet | https://api.mainnet-beta.solana.com |
| Custom | Your own RPC provider URL |
Warning
Make sure to update the USDC mint address when switching to mainnet:
- Devnet:
4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU - Mainnet:
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
# Run the development server
npm run dev
# Build for production
npm run build
# Start production server
npm start
# Lint the code
npm run lintThe project uses Tailwind CSS with custom theme extensions. Edit tailwind.config.ts:
theme: {
extend: {
colors: {
lazor: {
purple: '#8B5CF6', // Primary brand color
pink: '#EC4899', // Accent color
dark: '#0F0B1A', // Background
},
},
},
}Use the pre-built .glass-card class for frosted glass effects:
<div className="glass-card p-6">
Your content here
</div>| Property | Type | Description |
|---|---|---|
connect |
() => Promise<void> |
Opens passkey authentication |
loginWithPasskey |
() => Promise<void> |
Alias for connect |
address |
string | null |
User's Solana wallet address |
isConnected |
boolean |
Whether user is authenticated |
signAndSendTransaction |
(tx, options?) => Promise<string> |
Sign and submit a transaction |
| Option | Type | Default | Description |
|---|---|---|---|
paysFee |
boolean |
true |
Set to false for gasless transactions |
We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.