Skip to content

lamb356/lazorpay

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

LazorPay - Gasless Solana Payments with Passkey Authentication

A complete Next.js demo application showcasing the LazorKit SDK for building passkey-authenticated, gasless payment experiences on Solana.

πŸ”— Live Demo | πŸ“– LazorKit Docs

LazorPay Demo Next.js TypeScript

✨ Features

  • πŸ” Passkey Authentication - Sign in with Face ID, Touch ID, or Windows Hello
  • ⚑ Gasless Transactions - Send SOL without worrying about gas fees
  • πŸ“± QR Payment Requests - Generate QR codes to receive payments
  • 🎨 Embeddable Widget - Drop-in payment component for any app
  • πŸ“Š Transaction History - Track all your payments in real-time

πŸš€ Quick Start

Prerequisites

  • Node.js 18+
  • A WebAuthn-capable browser (Chrome, Safari, Edge, Firefox)
  • A device with biometric authentication (fingerprint/face) or security key

Installation

# Clone the repository
git clone https://github.com/YOUR_USERNAME/lazorpay.git
cd lazorpay

# Install dependencies
npm install

# Start development server
npm run dev

Open http://localhost:3000 to see the app.

πŸ“š Tutorials

Tutorial 1: Setting Up LazorKit in Your React App

This tutorial shows how to integrate LazorKit passkey authentication into any React/Next.js application.

Step 1: Install Dependencies

npm install @lazorkit/wallet @coral-xyz/anchor @solana/web3.js buffer

Step 2: Create Buffer Polyfill

Create src/polyfills.ts:

import { Buffer } from 'buffer';

if (typeof window !== 'undefined') {
  window.Buffer = Buffer;
}

export {};

Import this in your app's entry point (e.g., layout.tsx).

Step 3: Create the Provider

Create src/context/LazorPayProvider.tsx:

"use client";

import { LazorkitProvider, useWallet } from "@lazorkit/wallet";
import { createContext, useContext, ReactNode } from "react";

const CONFIG = {
  rpcUrl: "https://api.devnet.solana.com",
  portalUrl: "https://portal.lazor.sh",
  paymasterUrl: "https://lazorkit-paymaster.onrender.com",
};

// Create your custom context wrapping LazorKit
const WalletContext = createContext<ReturnType<typeof useWallet> | null>(null);

function WalletInner({ children }: { children: ReactNode }) {
  const wallet = useWallet();
  return (
    <WalletContext.Provider value={wallet}>
      {children}
    </WalletContext.Provider>
  );
}

export function LazorPayProvider({ children }: { children: ReactNode }) {
  return (
    <LazorkitProvider
      rpcUrl={CONFIG.rpcUrl}
      ipfsUrl={CONFIG.portalUrl}
      paymasterUrl={CONFIG.paymasterUrl}
    >
      <WalletInner>{children}</WalletInner>
    </LazorkitProvider>
  );
}

export function useWalletContext() {
  const context = useContext(WalletContext);
  if (!context) throw new Error("Must be within LazorPayProvider");
  return context;
}

Step 4: Wrap Your App

In layout.tsx:

import { LazorPayProvider } from "@/context/LazorPayProvider";
import "@/polyfills";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <LazorPayProvider>{children}</LazorPayProvider>
      </body>
    </html>
  );
}

Step 5: Use the Wallet

"use client";
import { useWalletContext } from "@/context/LazorPayProvider";

export function ConnectButton() {
  const { isConnected, connect, disconnect, smartWalletPubkey } = useWalletContext();

  if (isConnected) {
    return (
      <div>
        <p>Connected: {smartWalletPubkey?.toBase58()}</p>
        <button onClick={disconnect}>Disconnect</button>
      </div>
    );
  }

  return <button onClick={connect}>Connect with Passkey</button>;
}

Tutorial 2: Sending Gasless SOL Transfers

Learn how to send SOL using LazorKit's gasless transaction feature.

Step 1: Create the Transfer Function

import { SystemProgram, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";

async function sendSol(
  wallet: ReturnType<typeof useWallet>,
  recipient: string,
  amountSol: number
) {
  if (!wallet.smartWalletPubkey || !wallet.signAndSendTransaction) {
    throw new Error("Wallet not connected");
  }

  // Create transfer instruction
  const instruction = SystemProgram.transfer({
    fromPubkey: wallet.smartWalletPubkey,
    toPubkey: new PublicKey(recipient),
    lamports: Math.floor(amountSol * LAMPORTS_PER_SOL),
  });

  // Sign and send - gas is covered by LazorKit paymaster!
  const signature = await wallet.signAndSendTransaction(instruction);
  
  return signature;
}

Step 2: Build the UI

"use client";
import { useState } from "react";
import { useWalletContext } from "@/context/LazorPayProvider";

export function SendForm() {
  const wallet = useWalletContext();
  const [recipient, setRecipient] = useState("");
  const [amount, setAmount] = useState("");
  const [status, setStatus] = useState<"idle" | "sending" | "success" | "error">("idle");

  const handleSend = async () => {
    setStatus("sending");
    try {
      const sig = await sendSol(wallet, recipient, parseFloat(amount));
      console.log("Transaction:", sig);
      setStatus("success");
    } catch (err) {
      console.error(err);
      setStatus("error");
    }
  };

  return (
    <div>
      <input
        placeholder="Recipient address"
        value={recipient}
        onChange={(e) => setRecipient(e.target.value)}
      />
      <input
        type="number"
        placeholder="Amount (SOL)"
        value={amount}
        onChange={(e) => setAmount(e.target.value)}
      />
      <button onClick={handleSend} disabled={status === "sending"}>
        {status === "sending" ? "Sending..." : "Send SOL"}
      </button>
      {status === "success" && <p>βœ… Transaction sent!</p>}
      {status === "error" && <p>❌ Transaction failed</p>}
    </div>
  );
}

Tutorial 3: Creating an Embeddable Payment Widget

Build a reusable payment component that can be dropped into any app.

The PaymentWidget Component

interface PaymentWidgetProps {
  recipient: string;      // Wallet to receive payment
  amount: number;         // Amount in SOL
  memo?: string;          // Optional payment description
  onSuccess?: (sig: string) => void;
  onError?: (err: Error) => void;
}

export function PaymentWidget({
  recipient,
  amount,
  memo,
  onSuccess,
  onError,
}: PaymentWidgetProps) {
  const wallet = useWalletContext();
  const [status, setStatus] = useState<"idle" | "paying" | "success" | "error">("idle");

  const handlePay = async () => {
    // Connect if not connected
    if (!wallet.isConnected) {
      await wallet.connect();
    }

    setStatus("paying");
    try {
      const sig = await sendSol(wallet, recipient, amount);
      setStatus("success");
      onSuccess?.(sig);
    } catch (err) {
      setStatus("error");
      onError?.(err as Error);
    }
  };

  return (
    <div className="payment-widget">
      <h3>{amount} SOL</h3>
      <p>To: {recipient.slice(0, 8)}...</p>
      {memo && <p>"{memo}"</p>}
      
      <button onClick={handlePay} disabled={status === "paying"}>
        {wallet.isConnected ? `Pay ${amount} SOL` : "Connect & Pay"}
      </button>
      
      {status === "success" && <p>Payment successful! πŸŽ‰</p>}
    </div>
  );
}

Usage in Your App

// Checkout page
<PaymentWidget
  recipient="7BeWr6tVa1pYgrEddekYTnQENU22bBw9H8HYJUkbrN71"
  amount={0.5}
  memo="Order #12345"
  onSuccess={(sig) => {
    router.push(`/order-complete?tx=${sig}`);
  }}
/>

πŸ—οΈ Project Structure

lazorpay/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ layout.tsx       # App layout with providers
β”‚   β”‚   β”œβ”€β”€ page.tsx         # Main page
β”‚   β”‚   └── globals.css      # Global styles
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ ui/              # Base UI components
β”‚   β”‚   β”œβ”€β”€ ConnectButton.tsx
β”‚   β”‚   β”œβ”€β”€ SendPayment.tsx
β”‚   β”‚   β”œβ”€β”€ PaymentRequest.tsx
β”‚   β”‚   β”œβ”€β”€ PaymentWidget.tsx
β”‚   β”‚   └── TransactionHistory.tsx
β”‚   β”œβ”€β”€ context/
β”‚   β”‚   └── LazorPayProvider.tsx
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   └── utils.ts
β”‚   └── polyfills.ts
β”œβ”€β”€ public/
β”œβ”€β”€ package.json
└── README.md

πŸ”§ Configuration

Environment Variables (Optional)

For production, you may want to configure:

NEXT_PUBLIC_RPC_URL=https://api.devnet.solana.com
NEXT_PUBLIC_PORTAL_URL=https://portal.lazor.sh
NEXT_PUBLIC_PAYMASTER_URL=https://lazorkit-paymaster.onrender.com

Switching to Mainnet

Update the config in LazorPayProvider.tsx:

const CONFIG = {
  rpcUrl: "https://api.mainnet-beta.solana.com",
  // ... other mainnet configs from LazorKit
};

⚠️ Note: LazorKit is currently in beta and supports Devnet only. Do not use in production!

πŸ› οΈ Tech Stack

  • Framework: Next.js 15 (App Router)
  • Language: TypeScript
  • Styling: Tailwind CSS
  • Wallet: @lazorkit/wallet
  • Blockchain: Solana (Devnet)
  • Icons: Lucide React
  • QR Codes: qrcode.react

πŸ“¦ Dependencies

{
  "@lazorkit/wallet": "^1.4.x",
  "@coral-xyz/anchor": "^0.30.x",
  "@solana/web3.js": "^1.95.x",
  "buffer": "^6.0.x",
  "qrcode.react": "^4.x",
  "lucide-react": "^0.x"
}

🚒 Deployment

Vercel (Recommended)

# Install Vercel CLI
npm i -g vercel

# Deploy
vercel

Other Platforms

This is a standard Next.js app and can be deployed to:

  • Netlify
  • AWS Amplify
  • Railway
  • Any Node.js hosting

πŸ”— Resources

πŸ“„ License

MIT License - feel free to use this code in your own projects!

πŸ™ Acknowledgments

Built with LazorKit - The open-source passkey wallet infrastructure for Solana.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages