A reference implementation for Africa Free Routing 2025 (Accra) bootcamp students demonstrating how to interact with a Lightning Network node using gRPC and Next.js.
This application demonstrates how to:
- Connect to an LND node using gRPC
- Generate Lightning invoices
- Monitor invoice payment status
- Build a modern UI for Lightning payments
- Explore Bitcoin blockchain data in real-time
- Monitor mempool statistics and transaction fees
- Node.js v18 or later
- Access to an LND node (we use Polar for development)
- Access to a Bitcoin Core node
- Basic understanding of TypeScript and React
- Basic understanding of the Lightning Network and Bitcoin blockchain
- Clone the repository:
git clone https://github.com/Extheoisah/sample-ln-invoice-generator.git
cd lightning-invoice-app
- Install dependencies:
npm install
-
Configure your LND connection:
- Open
src/lib/lnd.ts
- Update the
DEFAULT_CONFIG
with your LND node's:rpcServer
addresstlsCertPath
locationmacaroonPath
location
- Open
-
Configure your Bitcoin Core connection:
- Open
src/lib/bitcoin.ts
- Update the
DEFAULT_CONFIG
with your Bitcoin node's:host
addressport
numberusername
andpassword
network
type (mainnet/testnet)
- Open
-
Start the development server:
npm run dev
src/
├── app/ # Next.js app directory
│ ├── api/ # API routes
│ │ ├── invoice/ # Invoice-related endpoints
│ │ └── bitcoin/ # Bitcoin-related endpoints
│ └── components/ # React components
├── lib/
│ ├── lnd.ts # LND gRPC client
│ ├── bitcoin.ts # Bitcoin Core client
│ └── utils.ts # Utility functions
└── types.ts # TypeScript type definitions
The LndClient
class in src/lib/lnd.ts
handles the gRPC connection to your Lightning node:
class LndClient {
// Establishes secure connection using TLS cert and macaroon
private buildServices(): LndServices {
const tlsCert = fs.readFileSync(this.config.tlsCertPath);
const macaroon = fs.readFileSync(this.config.macaroonPath);
// ... setup gRPC connection
}
// Create an invoice
async createInvoice(amount: number, memo: string, expiry: number): Promise<LndInvoice>
// Check payment status
async checkInvoiceStatus(rHash: Buffer): Promise<LndInvoice>
}
The BlockchainExplorer
component in src/app/components/blockchain-explorer.tsx
provides a real-time view of the Bitcoin blockchain:
- Live block visualization with drag-to-scroll interface
- Detailed block information including:
- Block height and hash
- Transaction count and fees
- Block size and weight
- Mining difficulty
- Timestamp and miner information
- Transaction details within blocks
- Real-time mempool statistics:
- Unconfirmed transaction count
- Memory usage
- Fee rates (low, medium, high, urgent priority)
- Minimum relay fees
// Example: Fetching block data
const blockResponse = await fetch("/api/bitcoin/blocks?limit=10");
const blockData = await blockResponse.json();
// Example: Fetching mempool statistics
const mempoolResponse = await fetch("/api/bitcoin/mempool");
const mempoolData = await mempoolResponse.json();
To generate an invoice:
- The frontend sends amount and memo to
/api/invoice
- The API calls
lndClient.createInvoice()
- Returns a payment request and other invoice details
The app demonstrates two important patterns:
- Converting between Buffer and hex strings for gRPC communication
- Polling for payment status using
setInterval
POST /api/invoice
Body: {
amount: number; // Amount in satoshis
memo: string; // Invoice description
expiry?: number; // Expiry in seconds (default: 3600)
}
GET /api/invoice/[rHash]/status
Response: {
isPaid: boolean;
}
const invoice = await lndClient.createInvoice(
1000, // amount in sats
"Test payment", // memo
3600 // expiry in seconds
);
const status = await lndClient.checkInvoiceStatus(rHashBuffer);
const isPaid = status.state === "SETTLED" || status.settled === true;
// From Buffer to hex string
const hexString = buffer.toString("hex");
// From hex string to Buffer
const buffer = Buffer.from(hexString, "hex");
- Error Handling: Always wrap LND calls in try-catch blocks
- Type Safety: Use TypeScript interfaces for all LND responses
- Buffer Handling: Properly convert between Buffer and hex strings
- Payment Monitoring: Implement proper cleanup for payment polling
- Security: Never expose macaroons or TLS certs in the frontend
-
Connection Failed
- Check if LND node is running
- Verify TLS cert and macaroon paths
- Ensure proper permissions on cert files
-
Type Errors
- LND returns snake_case properties
- Convert to camelCase for frontend use
- Use proper TypeScript interfaces
-
Buffer Handling
- gRPC expects Buffer for binary data
- Frontend needs hex strings
- Always convert appropriately
This is a reference implementation for educational purposes. If you find bugs or have improvements:
- Open an issue
- Submit a pull request
- Update documentation
MIT License - Feel free to use this code for learning and development.