nips/69.md

293 lines
11 KiB
Markdown
Raw Normal View History

2024-09-01 10:52:29 -04:00
NIP-69
======
2024-08-26 22:54:45 -04:00
Nostr Offer STRings
-------------------
2024-09-07 18:19:25 -04:00
`rfc` `optional`
2024-08-26 22:54:45 -04:00
2024-08-30 15:25:35 -04:00
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.
2024-08-26 22:54:45 -04:00
## 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.
2024-09-01 10:52:29 -04:00
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.
2024-08-26 22:54:45 -04:00
## Specification
### Static Payment Code Format
2024-09-01 10:52:29 -04:00
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:
2024-08-26 22:54:45 -04:00
2024-08-30 15:25:35 -04:00
- `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.
2024-09-01 10:52:29 -04:00
- `2`: The offer identifying string.
2024-09-02 13:53:30 -04:00
- `3`: A flag indicating the pricing type:
2024-09-08 14:14:32 -04:00
- `0`: Fixed price (Price stated in 4 will not change)
- `1`: Variable price (Price must be calculated and will be reflected in the invoice)
- `2`: Spontaneous payment (Payer specifies the amount in request payload)
2024-09-03 12:51:33 -04:00
- `4`: The price in sats (optional for display purposes).
2024-09-01 10:52:29 -04:00
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: <receiver_public_key_in_hex>
1: <relay_url>
2: <offer_id_string>
3: <pricing_type_flag> (optional, 0 for fixed, 1 for variable, 2 for spontaneous)
4: <price_in_sats> (optional)
```
2024-08-26 22:54:45 -04:00
2024-09-03 12:51:33 -04:00
## Integration with other NIPs
2024-08-30 16:02:49 -04:00
2024-09-03 12:51:33 -04:00
### NIP-01 User Metadata
2024-09-01 10:52:29 -04:00
2024-09-03 12:51:33 -04:00
Example user metadata content with `nip69` field:
2024-09-02 10:43:51 -04:00
```json
{
"pubkey": "hex_pub",
"kind": 0,
"content": "{\"name\": \"bob\", \"nip05\": \"bob@example.com\", \"nip69\": \"noffer1...\"}"
2024-09-03 16:34:05 -04:00
// ...
2024-09-02 10:43:51 -04:00
}
```
2024-09-03 12:51:33 -04:00
### NIP-05 "Lightning Addresses"
To support trust-minimized 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.
2024-09-02 10:43:51 -04:00
Example NIP-05 Service Response with Offers
2024-09-01 10:52:29 -04:00
```json
{
"names": {
2024-09-02 10:43:51 -04:00
"bob": "hex_pub"
2024-09-01 10:52:29 -04:00
},
2024-09-02 10:43:51 -04:00
"nip69": {
2024-09-02 11:28:36 -04:00
"bob": "noffer1..."
2024-09-02 10:43:51 -04:00
}
2024-09-01 10:52:29 -04:00
}
```
2024-08-30 16:02:49 -04:00
2024-09-03 12:51:33 -04:00
### NIP-57 Zaps
Instead of using the LNURL-pay callback, the "zap" flow can be initiated as content payload to an Offer:
1. The client creates a kind 9734 zap request event as specified in NIP-57.
2. Instead of sending this to an LNURL-pay endpoint, the client sends it as the `zap` part of the encrypted content in a NIP-69 payment request.
3. The receiver processes the NIP-69 payment request, decrypts the content, extracts the kind 9734 event, and uses it to generate the invoice.
4. The receiver responds with the invoice as specified in NIP-69.
5. Once paid, the receiver generates and publishes the kind 9735 zap receipt as specified in NIP-57.
Example NIP-57 zap request:
```json
{
"id": "<event_id>",
"pubkey": "<sender_pubkey>",
"created_at": 1234567890,
"kind": 21001,
"tags": [
["p", "<receiver_pubkey>"]
],
2024-09-03 13:44:01 -04:00
"content": "<NIP-44 encrypted {\"offer\":\"<zap1234>\",\"zap\":<kind 9734 zap request event>}>",
2024-09-03 12:51:33 -04:00
"sig": "<signature>"
}
```
2024-09-03 16:34:05 -04:00
There is no inherent requirement on the service to acknowledge zap requests, since spontaneous payments are default behavior. However, both Clients and Service implementations SHOULD consider offer identifiers, where `startsWith("zap")`, indication that the service will honor zap requests for that offer. A service should not error if no zap request was included by the sender on a zap indicated offer, the service should treat it as any other spontaneous payment.
2024-09-03 13:44:01 -04:00
2024-09-03 12:51:33 -04:00
## General Process Flow
2024-08-26 22:54:45 -04:00
1. **Payer Scans or Clicks the Static Payment Code**
2024-08-30 15:25:35 -04:00
2. **Payer's Wallet Decodes the Payment Code**
3. **Payer's Wallet Sends a Nostr Event to the Specified Relay**
2024-09-01 10:52:29 -04:00
- Addressed to the receiver's public key, containing the offer identifying string.
2024-08-26 22:54:45 -04:00
- Event Type: Ephemeral Kind 21001 | NIP-44 Encrypted
2024-09-01 10:52:29 -04:00
- **Conditional**: Include the amount in sats the sender wishes to pay if the payment type is spontaneous.
2024-08-30 15:25:35 -04:00
- Optional: Include additional payer data.
2024-08-26 22:54:45 -04:00
4. **Receiver Responds with Lightning Invoice**
2024-08-30 15:25:35 -04:00
- Generates a Lightning invoice and responds with a Nostr event containing the invoice details.
2024-08-26 22:54:45 -04:00
- Event Type: Ephemeral Kind 21001 | NIP-44 Encrypted
2024-09-01 10:52:29 -04:00
- **Content**: `{"bolt11":"<BOLT11_invoice_string>"}`
2024-08-30 15:25:35 -04:00
- Optional: Include additional purchase data.
2024-08-26 22:54:45 -04:00
5. **Payer Pays the Invoice**
2024-08-30 15:25:35 -04:00
- Completes the payment by settling the Lightning invoice.
2024-08-26 22:54:45 -04:00
6. **Optional: Receiver Emits Payment Receipt**
- This step is out of scope for this NIP, but potential receipt considerations include:
2024-08-30 15:25:35 -04:00
- Nostr Event as Receipt: Emitting a public Nostr event as a "zap" receipt for verifiable record-keeping.
2024-09-01 10:52:29 -04:00
- Lightning Pre-Image: The Lightning pre-image is proof of payment.
2024-08-30 15:25:35 -04:00
- External / WebHook: The receiver may finalize the flow out-of-band.
2024-08-26 22:54:45 -04:00
2024-09-07 18:19:25 -04:00
### Nostr Events
2024-08-26 22:54:45 -04:00
This NIP specifies the use of event kind `21001` with the following structure:
2024-08-30 15:25:35 -04:00
- `content`: NIP-44 encrypted payment details.
2024-08-26 22:54:45 -04:00
- `tags`:
2024-08-30 16:02:49 -04:00
- `p`: Receiver's public key (hex).
2024-09-01 10:52:29 -04:00
- `e`: Used in response to the requesting event.
2024-08-26 22:54:45 -04:00
2024-09-07 18:19:25 -04:00
Example request event:
2024-08-30 15:25:35 -04:00
2024-08-26 22:54:45 -04:00
```json
{
"id": "<event_id>",
"pubkey": "<sender_pubkey>",
"created_at": 1234567890,
"kind": 21001,
"tags": [
2024-09-07 18:19:25 -04:00
["p", "<receiver_pubkey>"]
2024-08-26 22:54:45 -04:00
],
2024-09-01 10:52:29 -04:00
"content": "<NIP-44 encrypted offer identifier and conditional payer data>",
2024-08-26 22:54:45 -04:00
"sig": "<signature>"
}
2024-08-30 16:06:40 -04:00
```
2024-08-26 22:54:45 -04:00
2024-09-08 14:37:17 -04:00
The `content` field, after NIP-44 decryption, should contain stringified JSON with the following structure:
2024-08-26 22:54:45 -04:00
2024-08-30 16:06:40 -04:00
```json
2024-08-26 22:54:45 -04:00
{
2024-08-30 15:25:35 -04:00
"offer": "<offer_string>",
2024-09-01 10:52:29 -04:00
"amount": "<conditional_amount_in_sats>",
2024-08-30 15:25:35 -04:00
"payer_data": "<optional_payer_data>"
2024-08-26 22:54:45 -04:00
}
```
2024-09-01 10:52:29 -04:00
Example response event with `bolt11` invoice:
```json
{
"id": "<response_event_id>",
"pubkey": "<receiver_pubkey>",
"created_at": 1234567891,
"kind": 21001,
"tags": [
["p", "<sender_pubkey>"],
["e", "<event_id>"] // original payment request event ID
],
"content": "<NIP-44 encrypted {\"bolt11\":\"<BOLT11_invoice_string>\"}>",
"sig": "<signature>"
}
```
2024-08-26 22:54:45 -04:00
## Client Behavior
2024-08-30 15:25:35 -04:00
### Mandatory
2024-08-26 22:54:45 -04:00
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.
2024-08-30 15:25:35 -04:00
6. Respect the relay URL specified in the static payment code for sending payment request events.
### Optional
2024-08-26 22:54:45 -04:00
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.
2024-09-03 14:45:46 -04:00
- Ex: If an offer lists multiple recipient keys, a client might "for each" over them to pay multiple recipients.
2024-09-03 16:34:05 -04:00
3. Use Addressable Events to nest offers in marketplace products etc where the offer variables might change more frequently than the product or service associated with it.
4. **Attempt Payments by Name**: Wallets might first attempt the combination of NIP-05 and NIP-69 before falling back to LNURL on name-based lookups.
5. Prefix and parse `noffer` strings with existing `lightning:` link handlers, just as they would invoices or legacy LNURL strings.
2024-08-26 22:54:45 -04:00
2024-08-30 15:25:35 -04:00
### Prohibited
2024-08-26 22:54:45 -04:00
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.
2024-08-30 16:02:49 -04:00
## 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.
2024-09-03 16:34:05 -04:00
2. **NIP-05 Integration**: Add a `nip69` field in the NIP-05 content to provide the offer on name-based lookups for trust-minimized Lightning Addresses.
2024-08-30 16:02:49 -04:00
2024-08-30 15:25:35 -04:00
## 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.
2024-09-01 11:08:02 -04:00
5. **Invalid Amount**: When the amount specified is too big or too small, providing the acceptable range.
2024-08-30 15:25:35 -04:00
2024-09-08 11:09:06 -04:00
Error codes:
- 1: Invalid Offer
- 2: Temporary Failure
- 3: Expired Offer
- 4: Unsupported Feature
- 5: Invalid Amount
Clients MUST handle these error responses gracefully and display appropriate messages to users.
### Error Response Payloads
2024-08-30 15:25:35 -04:00
Error responses should be sent as kind 21001 events with the following structure in the encrypted content:
```json
{
"error": "<error_message>",
2024-09-08 11:09:06 -04:00
"code": "<error_code>"
// Additional fields optionally included depending on the error type
}
```
### Expected Payloads
3. **Expired Offer**
- **Code**: 3
- **Payload**:
```json
{
"error": "Expired Offer",
"code": 3,
"latest": "<noffer1..>"
}
```
5. **Invalid Amount**
- **Code**: 5
- **Payload**:
```json
{
"error": "Invalid Amount",
"code": 5,
2024-09-07 18:19:25 -04:00
"range": { //sats
"min": 10,
"max": 10000000
2024-09-01 11:08:02 -04:00
}
2024-08-30 15:25:35 -04:00
}
```
## Transition from LNURL
2024-08-26 22:54:45 -04:00
2024-09-03 16:34:05 -04:00
LNURL services wishing to signal the upgrade should add `nip69: <noffer>` to the inital GET response described in [LUD-06](https://github.com/lnurl/luds/blob/luds/06.md). Clients seeing this new value on a LNURL server should automatically upgrade the payment flow that was instantiated by the user for that address.
2024-08-26 22:54:45 -04:00
2024-08-30 15:25:35 -04:00
## Future Considerations
2024-08-26 22:54:45 -04:00
2024-09-03 14:45:46 -04:00
Future versions of this NIP may consider standardizing additional features such as:
2024-08-26 22:54:45 -04:00
2024-09-03 14:45:46 -04:00
1. Multi-recipient payments with weighting.
2024-08-30 15:25:35 -04:00
2. Integration with other Lightning-related NIPs.
3. Extended metadata for more complex payment scenarios.
2024-09-03 14:45:46 -04:00
4. Best practices for use with Addressable Events.
2024-08-26 22:54:45 -04:00
2024-09-07 18:19:25 -04:00
## Reference Implementations
2024-08-26 22:54:45 -04:00
2024-09-08 11:09:06 -04:00
- [Lightning.Pub](https://github.com/shocknet/Lightning.Pub/pull/727) / [ShockWallet](https://github.com/shocknet/wallet2/pull/268)
- [demo.nip69.dev](https://demo.nip69.dev)