diff --git a/src/extensions/context.ts b/src/extensions/context.ts index 3916eb1e..b1c6e8d6 100644 --- a/src/extensions/context.ts +++ b/src/extensions/context.ts @@ -8,7 +8,8 @@ import { PaymentReceivedData, NostrEvent, UnsignedNostrEvent, - RpcMethodHandler + RpcMethodHandler, + LnurlPayInfo } from './types.js' /** @@ -44,6 +45,15 @@ export interface MainHandlerInterface { paymentHash: string feeSats: number }> + + /** + * Get LNURL-pay info for a user by their Nostr pubkey + * This enables Lightning Address (LUD-16) and zap (NIP-57) support + */ + getLnurlPayInfoByPubkey(pubkeyHex: string, options?: { + metadata?: string + description?: string + }): Promise } // Nostr operations @@ -177,6 +187,17 @@ export class ExtensionContextImpl implements ExtensionContext { return this.mainHandler.sendNostrEvent(event) } + /** + * Get LNURL-pay info for a user by pubkey + * Enables Lightning Address and zap support + */ + async getLnurlPayInfo(pubkeyHex: string, options?: { + metadata?: string + description?: string + }): Promise { + return this.mainHandler.paymentManager.getLnurlPayInfoByPubkey(pubkeyHex, options) + } + /** * Subscribe to payment received callbacks */ 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 diff --git a/src/extensions/types.ts b/src/extensions/types.ts index 86b5ebee..62abf5df 100644 --- a/src/extensions/types.ts +++ b/src/extensions/types.ts @@ -77,6 +77,20 @@ export interface PaymentReceivedData { metadata?: Record } +/** + * LNURL-pay info response (LUD-06/LUD-16) + * Used for Lightning Address and zap support + */ +export interface LnurlPayInfo { + tag: 'payRequest' + callback: string // URL to call with amount + minSendable: number // Minimum msats + maxSendable: number // Maximum msats + metadata: string // JSON-encoded metadata array + allowsNostr?: boolean // Whether zaps are supported + nostrPubkey?: string // Pubkey for zap receipts (hex) +} + /** * Nostr event structure (minimal) */ @@ -142,6 +156,15 @@ export interface ExtensionContext { */ publishNostrEvent(event: UnsignedNostrEvent): Promise + /** + * Get LNURL-pay info for a user (by pubkey) + * Used to enable Lightning Address support (LUD-16) and zaps (NIP-57) + */ + getLnurlPayInfo(pubkeyHex: string, options?: { + metadata?: string // Custom metadata JSON + description?: string // Human-readable description + }): Promise + /** * Subscribe to payment received callbacks */