Extension: Events (Ticketing) #10

Open
opened 2026-04-01 16:38:16 +00:00 by padreug · 1 comment
Owner

Summary

Implement a Nostr-native event ticketing extension for Lightning.Pub, replacing the LNbits events extension with a version that leverages Nostr for discovery and Lightning for payments.

Motivation

The LNbits events extension provides basic ticketing (create events, sell tickets via Lightning, promo codes, refunds) but is HTTP-only with no Nostr integration. A Lightning.Pub extension gets Nostr superpowers for free:

  • Events published as NIP-52 Calendar Events (kind 31923) — discoverable on any Nostr client
  • Ticket purchases via CLINK noffers instead of raw BOLT11
  • Attendee identity via Nostr pubkey (no email required)
  • Event announcements reach users through their existing Nostr feed

Scope

RPC Methods:

events.createEvent       events.listEvents
events.updateEvent       events.cancelEvent
events.purchaseTicket    events.listTickets
events.verifyTicket      events.refundTicket
events.createPromoCode   events.listPromoCodes

Data Model:

  • Event — title, description, date, location, capacity, price, status
  • Ticket — event_id, buyer_pubkey, payment_hash, status (pending/paid/refunded)
  • PromoCode — event_id, code, discount_percentage, max_uses, used_count

ExtensionContext API usage:

  • createInvoice() for ticket purchases
  • onPaymentReceived() to confirm tickets
  • payInvoice() for refunds
  • publishNostrEvent() to announce events (kind 31923)
  • registerMethod() for all CRUD operations

Complexity

LOW — ~800-1200 lines of TypeScript. Zero external dependencies. Pure payment + state tracking.

This is the simplest extension to implement and a good template for the extension pattern.

Reference

## Summary Implement a Nostr-native event ticketing extension for Lightning.Pub, replacing the LNbits `events` extension with a version that leverages Nostr for discovery and Lightning for payments. ## Motivation The LNbits events extension provides basic ticketing (create events, sell tickets via Lightning, promo codes, refunds) but is HTTP-only with no Nostr integration. A Lightning.Pub extension gets Nostr superpowers for free: - Events published as **NIP-52 Calendar Events (kind 31923)** — discoverable on any Nostr client - Ticket purchases via **CLINK noffers** instead of raw BOLT11 - Attendee identity via Nostr pubkey (no email required) - Event announcements reach users through their existing Nostr feed ## Scope **RPC Methods:** ``` events.createEvent events.listEvents events.updateEvent events.cancelEvent events.purchaseTicket events.listTickets events.verifyTicket events.refundTicket events.createPromoCode events.listPromoCodes ``` **Data Model:** - `Event` — title, description, date, location, capacity, price, status - `Ticket` — event_id, buyer_pubkey, payment_hash, status (pending/paid/refunded) - `PromoCode` — event_id, code, discount_percentage, max_uses, used_count **ExtensionContext API usage:** - `createInvoice()` for ticket purchases - `onPaymentReceived()` to confirm tickets - `payInvoice()` for refunds - `publishNostrEvent()` to announce events (kind 31923) - `registerMethod()` for all CRUD operations ## Complexity **LOW** — ~800-1200 lines of TypeScript. Zero external dependencies. Pure payment + state tracking. This is the simplest extension to implement and a good template for the extension pattern. ## Reference - LNbits events extension: `lnbits/extensions/events/` - NIP-52 Calendar Events: https://github.com/nostr-protocol/nips/blob/master/52.md - Existing withdraw extension as implementation reference: `lightning-pub/withdraw/`
Author
Owner

NIP Review: Applicable Standards for Events Extension

Core NIPs to Implement

NIP-52: Calendar Events — PRIMARY FOUNDATION

Use kind 31923 (time-based calendar events) as the canonical event format. This makes every event discoverable on any Nostr client (Amethyst, Damus, etc.) for free.

Event structure:

{
  "kind": 31923,
  "content": "<markdown event description>",
  "tags": [
    ["d", "<unique-event-id>"],
    ["title", "Bitcoin Meetup Guatemala"],
    ["summary", "Monthly Lightning Network meetup"],
    ["image", "<poster-url>"],
    ["location", "Café Barista, Antigua"],
    ["g", "<geohash>"],
    ["start", "<unix-timestamp>"],
    ["end", "<unix-timestamp>"],
    ["start_tzid", "America/Guatemala"],
    ["p", "<organizer-pubkey>", "", "organizer"],
    ["p", "<speaker-pubkey>", "", "performer"],
    ["t", "bitcoin"],
    ["t", "meetup"],
    ["price", "50000", "sats"]
  ]
}

Also defines:

  • Kind 31922: Date-based events (all-day, multi-day) — use for festivals/conferences
  • Kind 31924: Calendar collections (group related events)
  • Kind 31925: RSVPs (status: accepted/declined/tentative) — doubles as ticket confirmation

NIP-57: Zaps — PAYMENT LAYER

Zap receipts (kind 9735) serve as ticket purchase proof. The a tag links the payment directly to the calendar event:

{
  "kind": 9734,
  "tags": [
    ["a", "31923:<organizer-pubkey>:<event-d-id>"],
    ["amount", "<price-in-millisats>"],
    ["p", "<organizer-pubkey>"],
    ["relays", "wss://relay.example.com"]
  ]
}

Zap receipt (kind 9735) then becomes the immutable ticket — cryptographically signed payment proof linked to the specific event.

NIP-33: Parameterized Replaceable Events — INFRASTRUCTURE

Events (kind 31923) are addressable via <kind>:<pubkey>:<d-tag>. This means organizers can update event details (venue change, time shift) without invalidating existing tickets/RSVPs. Deletion via NIP-09.

Supplementary NIPs

NIP Kind Use Case Priority
NIP-72 (Communities) 34550 Venue/organizer brand community — ticket holders become members Optional
NIP-51 (Lists) 30003 User's "saved events" bookmark set Optional
NIP-38 (Statuses) 30315 Real-time event status ("Doors Open", "Sold Out") Nice-to-have
NIP-99 (Classifieds) 30402 Alternative listing format with price and status tags — less suitable than NIP-52 Skip
NIP-15 (Marketplace) 30017/30018 Order management patterns for group ticket purchases Reference only

Ticket purchase flow using standard NIPs:

  1. Organizer publishes event (kind 31923) with price tag
  2. Attendee zaps the event (kind 9734 request → kind 9735 receipt)
  3. Extension records zap receipt as ticket confirmation
  4. Attendee can also RSVP (kind 31925) for non-paid events or waitlists
  5. Organizer queries zap receipts by a tag to get attendee list

What this gives us over LNbits: Every event is automatically visible on Nostr, tickets are verifiable zap receipts, attendees are identified by npub, and RSVPs work across any Nostr client — zero custom UI required for basic discovery.

## NIP Review: Applicable Standards for Events Extension ### Core NIPs to Implement #### NIP-52: Calendar Events — PRIMARY FOUNDATION Use **kind 31923** (time-based calendar events) as the canonical event format. This makes every event discoverable on any Nostr client (Amethyst, Damus, etc.) for free. **Event structure:** ```json { "kind": 31923, "content": "<markdown event description>", "tags": [ ["d", "<unique-event-id>"], ["title", "Bitcoin Meetup Guatemala"], ["summary", "Monthly Lightning Network meetup"], ["image", "<poster-url>"], ["location", "Café Barista, Antigua"], ["g", "<geohash>"], ["start", "<unix-timestamp>"], ["end", "<unix-timestamp>"], ["start_tzid", "America/Guatemala"], ["p", "<organizer-pubkey>", "", "organizer"], ["p", "<speaker-pubkey>", "", "performer"], ["t", "bitcoin"], ["t", "meetup"], ["price", "50000", "sats"] ] } ``` Also defines: - **Kind 31922**: Date-based events (all-day, multi-day) — use for festivals/conferences - **Kind 31924**: Calendar collections (group related events) - **Kind 31925**: RSVPs (`status`: accepted/declined/tentative) — doubles as ticket confirmation #### NIP-57: Zaps — PAYMENT LAYER Zap receipts (kind 9735) serve as **ticket purchase proof**. The `a` tag links the payment directly to the calendar event: ```json { "kind": 9734, "tags": [ ["a", "31923:<organizer-pubkey>:<event-d-id>"], ["amount", "<price-in-millisats>"], ["p", "<organizer-pubkey>"], ["relays", "wss://relay.example.com"] ] } ``` Zap receipt (kind 9735) then becomes the immutable ticket — cryptographically signed payment proof linked to the specific event. #### NIP-33: Parameterized Replaceable Events — INFRASTRUCTURE Events (kind 31923) are addressable via `<kind>:<pubkey>:<d-tag>`. This means organizers can update event details (venue change, time shift) without invalidating existing tickets/RSVPs. Deletion via NIP-09. ### Supplementary NIPs | NIP | Kind | Use Case | Priority | |-----|------|----------|----------| | **NIP-72** (Communities) | 34550 | Venue/organizer brand community — ticket holders become members | Optional | | **NIP-51** (Lists) | 30003 | User's "saved events" bookmark set | Optional | | **NIP-38** (Statuses) | 30315 | Real-time event status ("Doors Open", "Sold Out") | Nice-to-have | | **NIP-99** (Classifieds) | 30402 | Alternative listing format with `price` and `status` tags — less suitable than NIP-52 | Skip | | **NIP-15** (Marketplace) | 30017/30018 | Order management patterns for group ticket purchases | Reference only | ### Recommended Implementation **Ticket purchase flow using standard NIPs:** 1. Organizer publishes event (kind 31923) with `price` tag 2. Attendee zaps the event (kind 9734 request → kind 9735 receipt) 3. Extension records zap receipt as ticket confirmation 4. Attendee can also RSVP (kind 31925) for non-paid events or waitlists 5. Organizer queries zap receipts by `a` tag to get attendee list **What this gives us over LNbits:** Every event is automatically visible on Nostr, tickets are verifiable zap receipts, attendees are identified by npub, and RSVPs work across any Nostr client — zero custom UI required for basic discovery.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: aiolabs/lightning-pub#10
No description provided.