feat(activities): organizer ticket scanner over Nostr transport #73
No reviewers
Labels
No labels
app:activities
app:chat
app:events
app:forum
app:libra
app:market
app:restaurant
app:tasks
app:wallet
app:webapp
bug
enhancement
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
aiolabs/webapp!73
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "ticket-scanner-nostr-webapp"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Closes the activities loop: organizers can now scan attendees'
QRs from the standalone PWA at the door, no LNbits admin UI
detour. Every scan publishes a kind-21000 RPC event signed with
the organizer's Nostr key — the signed event IS the
authorization, no admin_key in the browser, no HTTP path
involved on the happy path.
Companion to aiolabs/events#19 which adds the backend
events_ticket_registerRPC handler + secures the legacy HTTPregister endpoint.
Commits
02c1be0feat(base): NostrTransportService — nip44 v2 kind-21000 RPC client for LNbits0f8f98dfeat(activities): organizer ticket scanner over nostr-transportHow it works
ActivityDetailPage's top bar (gated onownedLnbitsEvent)./scan/:activityId(new). The page renders<QRScanner>(existing component usinguseQRScanner+qr-scannerv1.4.2 already inpackage.json).useTicketScannerstripsticket://, dedupsthe in-session list (localStorage
activities_scanned_<id>mirroring the LNbits admin page's pattern), then invokes
NostrTransportService.call('events_ticket_register', { event_id, ticket_id }).encrypts to the server pubkey, signs the kind-21000 event
with the user's Nostr key, publishes via the existing
RelayHub, and listens for the signed response.flips
registered = True, returns the updated ticket. Thewebapp shows a green banner with the holder's name (if
any) and appends to the scanned list.
Components
src/modules/base/services/NostrTransportService.ts—generic kind-21000 RPC client. Handles NIP-44 v2 encrypt /
decrypt (
nostr-tools'nip44.v2), signing with thecurrent user's
prvkeyviafinalizeEvent, shard reassemblyfor responses larger than the per-event cap, 15s default
timeout. Throws
NostrRpcErroron backend ERROR responses.src/modules/activities/composables/useTicketScanner.ts—stateful driver.
onDecodeis the QR handler;lastScanexposes{ status: 'ok' | 'duplicate-session' | 'error', ticketId, ticket?, message? };scannedpersiststhe per-activity registered list to localStorage.
src/modules/activities/views/ScanTicketsPage.vue—camera viewport + three-variant last-scan banner +
scrollable scanned list with timestamps and holder names.
ActivityDetailPage.vue—Scanbutton next to theexisting
Editaffordance, same gating(
ownedLnbitsEvent !== null)..env.example+src/lib/config/lnbits.ts—VITE_LNBITS_NOSTR_TRANSPORT_PUBKEYfor the server pubkey.Wire contract
Matches the backend PR exactly:
events_ticket_register{ event_id, ticket_id }Ticketdict"Ticket not paid for"/"Ticket already registered"/"Ticket does not exist on this event"/"You do not own this event"/"Wallet access required (provide wallet_id)"Backend dependency
Do not merge until aiolabs/events#19 has shipped and the
catalog entry is bumped on the host the webapp connects to.
Without the RPC handler registered, every scan attempt times
out at 15s.
Test plan
After backend is live on the target host:
in the detail page top bar.
entry appears in the scanned list with timestamp,
localStorage["activities_scanned_<id>"]carries it.this session", no second RPC fired.
"Ticket does not exist on this event".
/scan/<id>for an event owned by someone else → thebackend rejects with "You do not own this event" on first
scan (route is reachable but harmless).
error banner, scanned list state unchanged.
🤖 Generated with Claude Code
Generic client for LNbits's nostr-transport (landed upstream Sun May 24, commit f235966c). Encrypts a request envelope to the server's transport pubkey with NIP-44 v2, signs a kind-21000 event with the current user's Nostr key, publishes via RelayHub, and listens for a signed response addressed back to us. Shards (Lightning.Pub's `{part, index, totalShards, shardsId}` wrapper) are reassembled before parsing. Activities ticket scanner is the first consumer; wallet ops + event CRUD are obvious next adopters (file as follow-up). Server pubkey discovery is currently env-var (VITE_LNBITS_NOSTR_TRANSPORT_PUBKEY) — see also the follow-up to add a `.well-known` discovery endpoint on LNbits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>