LNURL Integration
This protocol implements the LUD-06 and LUD-16 specifications to bridge traditional Lightning Network clients with the Nostr identity ecosystem.
LUD-16: Internet Identifiers
LUD-16 allows Lightning wallets to resolve human-readable identifiers formatted like email addresses (e.g., user@example.com). Any compliant service can expose its identity graph to standard Lightning wallets through this mechanism.
Identifier Resolution
The username portion of the LUD-16 address can be one of three things, resolved in priority order:
| Priority | Type | Example | How it resolves |
|---|---|---|---|
| 1 | NIP-05 handle | alice@example.com | Looked up in the identity store by name |
| 2 | ConnectionKey hash | a1b2c3...@example.com (64-char hex) | Looked up in the connection store by hash |
| 3 | Raw pubkey | <hex_pubkey>@example.com (64-char hex) | Used directly as the recipient pubkey |
For social LIDP connections (Discord, GitHub, etc.), the identifier is the connectionKey hash — SHA256(lidp_name:lidp_id). Clients never expose the raw LIDP username in the LNURL path.
GET https://example.com/.well-known/lnurlp/<connectionKey>
The Callback Response
The service resolves the identifier via the priority table above, then responds with a standard LNURL payload:
{
"callback": "https://example.com/api/lnurl/cb/<connectionKey>",
"maxSendable": 100000000,
"minSendable": 1000,
"metadata": "[[\"text/plain\",\"Zap <connectionKey>@example.com\"],[\"text/identifier\",\"<connectionKey>@example.com\"],[\"chain/flokicoin\",\"loki\"]]",
"tag": "payRequest",
"commentAllowed": 205,
"allowsNostr": true,
"nostrPubkey": "<recipient_or_server_pubkey>"
}
Nostr Extensions
allowsNostr: truesignals to NIP-57 capable clients that they can submit a Kind 5520 Zap Request to the callback URL.- The
nostrPubkeyis the pubkey that will sign the resulting Kind 5521 Zap Receipt. This is typically the recipient's pubkey, or the server's pubkey for unclaimed/anonymous users.
Invoice Generation (Callback)
When the client accesses the callback URL (e.g., GET https://example.com/api/lnurl/cb/...&amount=10000&nostr=<url_encoded_kind_5520>), the server executes the following logic:
- Validation: Validate the Kind 5520 against the requested amount.
- Target Resolution: Is the target a registered user with a connected NWC wallet?
- Yes (Direct): The server acts as a passthrough, retrieving an invoice directly from the user's NWC node.
- No (Custodial): The server generates an invoice from its own Master Node (the Fallback Address flow).
- Invoice Response: The server returns the generated
pr(payment request) to the client.
{
"pr": "lnbc10n1...",
"routes": []
}
Multi-Chain Support
The standard LUD-06/LUD-16 metadata array is extended with chain tags to signal which Lightning Network chains and denominations the service accepts. This is a backward-compatible extension — existing Bitcoin-only wallets simply ignore unknown metadata entries and continue to function normally.
Chain Metadata Tags
Chain support is declared via optional entries in the metadata JSON array using the format chain/<chain_name>:
| Tag | Denomination | Description |
|---|---|---|
chain/bitcoin | sats or btc | Standard Bitcoin Lightning Network |
chain/flokicoin | loki | Flokicoin Lightning Network |
If no chain tags are present, clients default to chain/bitcoin. If at least one chain tag is present, clients should assume only the listed chains are supported.
Example: Flokicoin Service
A service operating on the Flokicoin Lightning Network includes the chain/flokicoin metadata tag with the loki denomination:
["chain/flokicoin", "loki"]
Amounts in minSendable and maxSendable are denominated in the smallest sub-unit of the declared chain, and the returned pr invoice conforms to the respective chain's Lightning invoice format.
| Chain | Unit Name | Example |
|---|---|---|
chain/bitcoin | millisatoshis (msat) | 1000 = 1 sat |
chain/flokicoin | milliloki (mloki) | 1000 = 1 loki |
Bitcoin Wallet Compatibility
Standard Bitcoin Lightning wallets that do not understand chain/flokicoin will silently ignore the tag and attempt to pay the returned invoice. This will fail at the protocol level because the invoice belongs to a different chain — no funds are lost, no cross-chain confusion is possible. The wallet simply rejects the invoice as unpayable.
When a service supports multiple chains, it includes multiple chain tags (e.g., both chain/bitcoin and chain/flokicoin), and the callback negotiates the correct chain based on the client's preference.