NIP-69 ====== Nostr Offer STRings ------------------- `wip` `optional` This NIP proposes a format for static payment codes in Nostr as a successor to LNURL-Pay, enabling users to initiate Lightning Network payments by scanning or clicking a string. ## Motivation Reliance on LNURL has led to centralization via custodial solutions due to the legacy baggage of IP4-NAT/Domain/SSL requirements. Nostr's use-cases are already centered around Lightning, whether as a wallet connector, zap receipts, or its overlapping network effects, rendering Nostr as a de facto "3rd layer" for Lightning and a natural successor to LNURL. When layered over kind 21000, NWC, or other potential Lightning RPCs, this specification enables a seamless user experience similar to legacy LNURL-Pay, without the domain and SSL requirements. Additionally, the signed nature of Nostr communications minimizes the trust requirements inherent in LNURL when a receiving node must outsource the serving of web requests. ## Specification ### Static Payment Code Format The static payment code is a bech32 (per [NIP-19](19.md)) encoded string prefixed with `noffer`. The encoded string will include the following TLV (Type-Length-Value) items: - `0`: The 32 bytes of the receiving service's public key, encoded in hex. - `1`: The relay URL where the receiving service subscribes to payment requests. - `2`: The offer identifying string. - `3`: The price in sats (optional). - `4`: A flag indicating the pricing type (optional): - `0`: Fixed price - `1`: Variable price - `2`: Spontaneous payment (payer specifies the amount) If neither the price nor the pricing type flag is present, the sender may assume it is a spontaneous payment offer. Example static payment code structure: ``` noffer1... 0: 1: 2: 3: (optional, 0 for fixed, 1 for variable, 2 for spontaneous) 4: (optional) ``` ### NIP-05 Integration To support trust-optimized Lightning Addresses, services can add a `nip69` field in the NIP-05 content to contain an offer for spontaneous payments. This field will provide the offer on name-based lookups. Example NIP-05 content with `nip69` field: ```json { "names": { "example": "npub1examplepublickey" }, "nip69": "noffer1..." } ``` ### Process Flow 1. **Payer Scans or Clicks the Static Payment Code** 2. **Payer's Wallet Decodes the Payment Code** 3. **Payer's Wallet Sends a Nostr Event to the Specified Relay** - Addressed to the receiver's public key, containing the offer identifying string. - Event Type: Ephemeral Kind 21001 | NIP-44 Encrypted - **Conditional**: Include the amount in sats the sender wishes to pay if the payment type is spontaneous. - Optional: Include additional payer data. 4. **Receiver Responds with Lightning Invoice** - Generates a Lightning invoice and responds with a Nostr event containing the invoice details. - Event Type: Ephemeral Kind 21001 | NIP-44 Encrypted - **Content**: `{"bolt11":""}` - Optional: Include additional purchase data. 5. **Payer Pays the Invoice** - Completes the payment by settling the Lightning invoice. 6. **Optional: Receiver Emits Payment Receipt** - This step is out of scope for this NIP, but potential receipt considerations include: - Nostr Event as Receipt: Emitting a public Nostr event as a "zap" receipt for verifiable record-keeping. - Lightning Pre-Image: The Lightning pre-image is proof of payment. - External / WebHook: The receiver may finalize the flow out-of-band. ### Nostr Event This NIP specifies the use of event kind `21001` with the following structure: - `content`: NIP-44 encrypted payment details. - `tags`: - `p`: Receiver's public key (hex). - `e`: Used in response to the requesting event. Example event: ```json { "id": "", "pubkey": "", "created_at": 1234567890, "kind": 21001, "tags": [ ["p", ""], ["e", ""] // used only in response by the receiver to identify the original payment request ], "content": "", "sig": "" } ``` The `content` field, after NIP-44 decryption, should contain an object with the following structure: ```json { "offer": "", "amount": "", "payer_data": "" } ``` Example response event with `bolt11` invoice: ```json { "id": "", "pubkey": "", "created_at": 1234567891, "kind": 21001, "tags": [ ["p", ""], ["e", ""] // original payment request event ID ], "content": "\"}>", "sig": "" } ``` ## Client Behavior ### Mandatory Clients implementing this NIP must: 1. Decode and validate the structure of the `noffer` bech32-encoded static payment code. 2. Use NIP-44 encryption for all communication between payer and receiver. 3. Generate and send kind 21001 events as specified in this NIP. 4. Parse the `content` field of received events by decrypting and JSON-parsing the stringified content. 5. Handle potential errors gracefully, providing clear feedback to users. 6. Respect the relay URL specified in the static payment code for sending payment request events. ### Optional Clients implementing this NIP may: 1. Support both this protocol and LNURL for backward compatibility during the transition period. 2. Implement additional features such as multi-recipient payments or integration with other Lightning-related NIPs. 3. **Use Addressable Events**: Implement addressable events to allow for updates to the recipient key or relay URL associated with the offer. 4. **Attempt Payments by Name**: Use the combination of NIP-05 and NIP-69 to attempt payments by name-based lookups. ### Prohibited Clients implementing this NIP MUST NOT: 1. Send unencrypted sensitive information in event content or tags. 2. Ignore or modify any fields from the encoded static payment code. ## Service Behavior ### Optional Services implementing this NIP may: 1. Use an account identifier as a default offer string for spontaneous payments without explicit user setup. 2. **NIP-05 Integration**: Add a `nip69` field in the NIP-05 content to provide the offer on name-based lookups for trust-optimized Lightning Addresses. ## Error Handling To ensure consistent error handling across implementations, this NIP defines the following error responses, similar to other NIPs: 1. **Invalid Offer**: When the offer ID is invalid or no longer available. 2. **Temporary Failure**: When the receiver is temporarily unable to process the request. 3. **Expired Offer**: When the offer has expired and is no longer valid. 4. **Unsupported Feature**: When the receiver doesn't support a feature requested by the payer. Error responses should be sent as kind 21001 events with the following structure in the encrypted content: ```json { "error": "", "code": "" } ``` Error codes: - 1: Invalid Offer - 2: Temporary Failure - 3: Expired Offer - 4: Unsupported Feature Clients MUST handle these error responses gracefully and display appropriate messages to users. ## Transition from LNURL LNURL services wishing to migrate to Nostr Offer STRings should consider adding a `noffer` tag to their responses. ## Future Considerations Future versions of this NIP may consider additional features such as: 1. Multi-recipient payments. 2. Integration with other Lightning-related NIPs. 3. Extended metadata for more complex payment scenarios. 4. **Addressable Events**: In Nostr apps where the offer is used, consider using addressable events to allow for updates to the recipient key or relay URL associated with the offer. ## Reference Implementation Lightning.Pub / ShockWallet offers branches