Package Exports
- @candypoets/lnuts
- @candypoets/lnuts/utils
Readme
lnuts
An LNURL-pay to Cashu mint bridge with Nostr integration. Allows anyone to claim npub@domain.com addresses that will receive Cashu tokens via Lightning payments.
Features
- LNURL-pay Endpoint: Serve
/.well-known/lnurlp/[alias]for Lightning payments - Nostr Integration: Claim addresses via Nostr kind 9322 events
- Cashu Mint Bridge: Automatically mint Cashu tokens when payments are received
- Token Delivery: Deliver tokens back to users via Nostr kind 9321 events
- Automatic Polling: Continuously polls for paid MintQuotes and delivers tokens
- Admin API: Monitor and manage the system via REST endpoints
- SQLite Storage: Persistent storage for claims and payments
Architecture
User → Lightning Payment → LNURL-pay → Cashu Mint → Nostr Delivery → User- Users claim
npub@domain.comvia Nostr kind 9322 events - Payments come in via LNURL-pay endpoint
- Cashu mint generates tokens when payment is confirmed
- Tokens are delivered back to user via kind 9321 events
Installation
npm install @candypoets/lnutsConfiguration
Create a .env file:
LNUTS_DOMAIN=mydomain.com
LNUTS_MINT_URL=https://mint.example.com
LNUTS_NOSTR_RELAYS=wss://relay.damus.io,wss://relay.nostr.band
LNUTS_ADMIN_SECRET_KEY=your_admin_secret_key_hereUsage
1. Add Middleware to hooks.server.ts
// src/hooks.server.ts
import { createLnutsMiddleware } from 'lnuts/server';
export const handle = createLnutsMiddleware();2. Create Required Routes
LNURL-pay requires specific routes to function. Create these in your SvelteKit app:
LNURL Metadata Endpoint
// src/routes/.well-known/lnurlp/[alias]/+server.ts
import { json } from '@sveltejs/kit';
import { handleLnurlPayRequest } from 'lnuts/server';
export async function GET({ params }) {
const result = await handleLnurlPayRequest(params.alias);
if (result.status === 'error') {
return json({ status: 'ERROR', reason: result.reason }, { status: 404 });
}
return json(result);
}LNURL Callback Endpoint
// src/routes/.well-known/lnurlp/[alias]/callback/+server.ts
import { json } from '@sveltejs/kit';
import { handleLnurlCallback } from 'lnuts/server';
export async function GET({ params, url }) {
const amount = parseInt(url.searchParams.get('amount') || '0');
const comment = url.searchParams.get('comment') || undefined;
const result = await handleLnurlCallback(params.alias, amount, comment);
if (result.status === 'error') {
return json({ status: 'ERROR', reason: result.reason }, { status: 400 });
}
return json(result);
}3. Handle Claim Events
For receiving Nostr claim events, create an endpoint to handle them:
// src/routes/api/claims/+server.ts
import { json } from '@sveltejs/kit';
import { handleClaimEvent } from 'lnuts/server';
export async function POST({ request }) {
const claimEvent = await request.json();
const result = await handleClaimEvent(claimEvent);
return json(result);
}Handler Class API
The main entry point is the LnutsHandler class which provides all functionality:
Constructor Options
interface LnutsOptions {
dbPath?: string; // Path to SQLite database file
mintUrl?: string; // Cashu mint URL
nostrRelays?: string[]; // Nostr relays to connect to
adminSecretKey?: string; // Nostr private key for signing events
domain?: string; // Domain name for LNURL metadata
autoStart?: boolean; // Whether to auto-start services
}Creating an Instance
import { LnutsHandler } from 'lnuts/server';
// Using environment variables
const lnuts = new LnutsHandler();
// With custom options
const lnuts = new LnutsHandler({
domain: 'mydomain.com',
mintUrl: 'https://mint.example.com',
adminSecretKey: process.env.LNUTS_ADMIN_SECRET_KEY,
dbPath: './data/lnuts.db',
});Properties
db: Database instance for direct queriestokenDelivery: Nostr token delivery servicemintClient: Cashu mint client for interacting with the Cashu mintoptions: Configuration options used by the handler
Methods
start(): Manually start all services (called automatically ifautoStart: true)handle: SvelteKit handle function for middleware integration. This method should be added to yourhooks.server.tsfile to enable LNURL-pay and claim event handling.The recommended way to use this is through the
createLnutsMiddleware()factory function:// src/hooks.server.ts import { createLnutsMiddleware } from 'lnuts/server'; export const handle = createLnutsMiddleware();
This creates a default
LnutsHandlerinstance and returns itshandleproperty.clone(options): Create a new handler instance with shared config
Automatic MintQuote Polling
The handler automatically polls for paid MintQuotes every 10 seconds (configurable). When a payment is detected as paid:
- The system mints Cashu tokens from the Cashu mint
- The tokens are stored in the database
- The tokens are automatically delivered to the recipient via Nostr
Direct Service Access
You can access individual services directly:
// In a route or server file
import { LnutsHandler } from 'lnuts/server';
const lnuts = new LnutsHandler();
// Access database directly
const claims = lnuts.db.prepare('SELECT * FROM claims').all();
// Use token delivery service
await lnuts.tokenDelivery.deliverTokens(
paymentId,
token,
recipientNpub,
amount
);
// Use mint client
const { quoteId, invoice } = await lnuts.mintClient.requestMintQuote(1000, 'sat');
// Access the handler's internal services
const { db, tokenDelivery, mintClient } = lnuts;Development
# Install dependencies
npm install
# Run development server
npm run dev
# Build for production
npm run build
# Preview production build
npm run previewLicense
MIT