JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 22
  • Score
    100M100P100Q63303F
  • License MIT

LNURL-pay to Cashu mint bridge with Nostr integration

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
  1. Users claim npub@domain.com via Nostr kind 9322 events
  2. Payments come in via LNURL-pay endpoint
  3. Cashu mint generates tokens when payment is confirmed
  4. Tokens are delivered back to user via kind 9321 events

Installation

npm install @candypoets/lnuts

Configuration

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_here

Usage

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 queries
  • tokenDelivery: Nostr token delivery service
  • mintClient: Cashu mint client for interacting with the Cashu mint
  • options: Configuration options used by the handler

Methods

  • start(): Manually start all services (called automatically if autoStart: true)

  • handle: SvelteKit handle function for middleware integration. This method should be added to your hooks.server.ts file 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 LnutsHandler instance and returns its handle property.

  • 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:

  1. The system mints Cashu tokens from the Cashu mint
  2. The tokens are stored in the database
  3. 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 preview

License

MIT