From b519c0e447c203639deef721f07cc7f49e9a47db Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Fri, 13 Feb 2026 15:04:59 -0500 Subject: [PATCH] feat(nip05): add Lightning Address support for zaps Adds /.well-known/lnurlp/:username endpoint that: 1. Looks up username in NIP-05 database 2. Gets LNURL-pay info from Lightning.Pub for that user 3. Returns standard LUD-16 response for wallet compatibility This makes NIP-05 addresses (alice@domain) work seamlessly as Lightning Addresses for receiving payments and NIP-57 zaps. Co-Authored-By: Claude Opus 4.5 --- src/extensions/nip05/index.ts | 73 +++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/extensions/nip05/index.ts b/src/extensions/nip05/index.ts index a37fe8e0..a8c1a7fd 100644 --- a/src/extensions/nip05/index.ts +++ b/src/extensions/nip05/index.ts @@ -94,6 +94,13 @@ export default class Nip05Extension implements Extension { method: 'GET', path: '/api/v1/nip05/nostr.json', handler: this.handleNostrJson.bind(this) + }, + // Lightning Address endpoint (LUD-16) + // Makes NIP-05 usernames work as Lightning Addresses for zaps + { + method: 'GET', + path: '/.well-known/lnurlp/:username', + handler: this.handleLnurlPay.bind(this) } ] } @@ -220,6 +227,72 @@ export default class Nip05Extension implements Extension { } } } + + /** + * Handle /.well-known/lnurlp/:username request (Lightning Address / LUD-16) + * + * This enables NIP-05 usernames to work as Lightning Addresses for receiving + * payments and zaps. When someone sends to alice@domain.com: + * 1. Wallet resolves /.well-known/lnurlp/alice + * 2. We look up alice -> pubkey in our NIP-05 database + * 3. We return LNURL-pay info from Lightning.Pub for that user + */ + private async handleLnurlPay(req: HttpRequest): Promise { + try { + const { username } = req.params + const appId = req.headers['x-application-id'] || 'default' + + if (!username) { + return { + status: 400, + body: { status: 'ERROR', reason: 'Username required' }, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + } + } + } + + // Look up the username in our NIP-05 database + const lookup = await this.manager.lookupUsername(appId, username) + + if (!lookup.found || !lookup.identity) { + return { + status: 404, + body: { status: 'ERROR', reason: 'User not found' }, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + } + } + } + + // Get LNURL-pay info from Lightning.Pub for this user's pubkey + const lnurlPayInfo = await this.ctx.getLnurlPayInfo(lookup.identity.pubkey_hex, { + description: `Pay to ${username}` + }) + + return { + status: 200, + body: lnurlPayInfo, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + 'Cache-Control': 'max-age=60' // Cache for 1 minute + } + } + } catch (error) { + this.ctx.log('error', `Error handling lnurlp: ${error}`) + return { + status: 500, + body: { status: 'ERROR', reason: 'Internal server error' }, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + } + } + } + } } // Export types for external use