nips/68.md
shocknet-justin ef24dd6156 68
2024-10-08 11:56:36 -04:00

400 lines
13 KiB
Markdown

NIP-68
======
Debit Requests
--------------
`wip` `rfc` `optional`
This NIP defines a method for applications and users to request Lightning payments with event kind `21002`. This complements [NIP-69](69.md) by allowing the inverse operation, and providing users with a static endpoint that applications and services can use for a more fluid and secure authorization UX than is currently available with LNURL or NWC.
## Motivation
Other pairing schemes have a flawed UX that forces the user into a pre-provisioning step before communicating the URI to a service. Not only is this unintuitive to users, it introduces intercept vectors through key-fumbling of the secret. User-issued keys are also difficult to scale from services perspective, as each client posessing a unique key necessitates a distinct communications processes. Terms of these connections are not trivially co-established, which limits utility in scenarios like recurring payments. Existing architectures also fail to leverage Nostr as a reputation system, undermining the ability of wallet clients to leverage rules based on any variety of graph data.
The ideal UX target with with NIP-68 (and 69) has been achieved in the below referenced demo: A user enters a NIP-05 or Lightning Address into a service -> Accepts a request notification in their wallet -> Connection with clear terms between wallet and service is now established.
## Specification
### Debit Request Pointers
A debit request pointer is a bech32 (per [NIP-19](19.md)) encoded string prefixed with `ndebit`.
The encoded string will include the following TLV (Type-Length-Value) items:
- `0`: The 32 bytes of the wallet service's public key, encoded in hex.
- `1`: The relay URL where the wallet service subscribes to payment requests.
- `2`: Pointer Identifier.
If the pointer ID is not declared, the requestor may assume that the listening key is acting as the identifier and omit the ID field from their request.
Example pointer structure:
```
ndebit1...
0: <wallet_service_pubkey_hex>
1: <relay_url>
2: <pointer_id>
```
## Integration with other NIPs
### NIP-01 User Metadata
Users might advertise this capability so that apps may have a default awareness of the users payment source.
Example user metadata content with `nip68` field:
```json
{
"pubkey": "hex_pub",
"kind": 0,
"content": "{\"name\": \"bob\", \"nip05\": \"bob@example.com\", \"nip68\": \"ndebit1...\"}"
// ...
}
```
### NIP-05 "Lightning Addresses"
To make connecting an app to your wallet as simple as pasting your Lightning Address, name services can add a `nip68` field in the NIP-05 content for pointer discovery on name-based lookups.
Example NIP-05 Service Response with Debit Request Pointers
```json
{
"names": {
"bob": "hex_pub"
},
"nip68": {
"bob": "ndebit1..."
}
}
```
## Nostr Events
This NIP specifies use of event kind `21002` with the following structure:
- `content`: NIP-44 encrypted request details.
- `tags`:
- `p`: Receiver's public key (hex).
- `e`: Used in response to the requesting event.
Example request event:
```json
{
"id": "<event_id>",
"pubkey": "<application_pubkey>",
"created_at": 1234567890,
"kind": 21002,
"tags": [
["p", "<wallet_pubkey>"]
],
"content": "<NIP-44 encrypted request>",
"sig": "<signature>"
}
```
The `content` field after NIP-44 decryption must contain one of the following structures:
1. For a payment request:
```json
{
"pointer": "<pointer_id>", // optionally undefined
"amount_sats": 1000000,
"bolt11": "<BOLT11_invoice_string>",
"description": "<optional_app_data>"
}
```
2. For a budget request:
```json
{
"pointer": "<pointer_id>", // optionally undefined
"amount_sats": 1000000,
"frequency": {
"number": 1,
"unit": "month" // Possible values: "day", "week", "month"
},
"description": "<optional_app_data>"
}
```
The wallet service MAY require `amount_sats` in order to process rules without first decoding the invoice, but then MUST ensure the invoice amount matches before completing the request.
For budget requests, the absence of the `frequency` field indicates a one-time budget that doesn't periodically reset.
**Requests with no amount, no frequency, and no invoice are a defacto request for full access.**
### Response events
Wallet services should respond with the preimage when a direct payment request is fulfilled, or simply "ok" when a budget request is approved.
The wallet service MAY respond with a **GFY** (General Failure to Yield) code when a request will not be fulfilled.
1. ACK with Lightning preimage (for direct payment):
```json
{
"id": "<response_event_id>",
"pubkey": "<wallet_pubkey>",
"created_at": 1234567891,
"kind": 21002,
"tags": [
["p", "<application_pubkey>"],
["e", "<event_id>"] // original request event ID
],
"content": "<NIP-44 encrypted {\"res\":\"ok\",\"preimage\":\"<lightning_preimage>\"}>",
"sig": "<signature>"
}
```
1b. ACK a budget request:
```json
{
"id": "<response_event_id>",
"pubkey": "<wallet_pubkey>",
"created_at": 1234567891,
"kind": 21002,
"tags": [
["p", "<application_pubkey>"],
["e", "<event_id>"] // original request event ID
],
"content": "<NIP-44 encrypted {\"res\":\"ok\"}>",
"sig": "<signature>"
}
```
2. GFY response:
```json
{ // ...
"content": "<NIP-44 encrypted {\"res\":\"GFY\",\"code\":2,\"error\":\"Unknown Lightning Failure\"}>",
"sig": "<signature>"
}
```
## GFY Handling
To facilitate consistent handling across implementations, this NIP defines the following GFY responses:
1. **Request Denied Warning**: When the request is denied by the wallet service. This serves as a warning to the requestor that they may be reported on subsequent attempts.
2. **Temporary Failure**: When the wallet service is temporarily unable to process the request.
3. **Expired Request**: When the delta between the request and wallet time (unix) is too great (Default 30s).
4. **Rate Limited**: The requestor has been rate-limited by the wallet.
5. **Invalid Amount**: When the amount specified is too big or too small, providing the acceptable range.
6. **Invalid Request**: When the request is in some way malformed.
### GFY Codes
- 1: Request Denied Warning
- 2: Temporary Failure
- 3: Expired Request
- 4: Rate Limited
- 5: Invalid Amount
- 6: Invalid Request
Clients MUST handle these responses gracefully and display appropriate messages to users.
### GFY Response Payloads
GFY responses must be sent as kind `21002` events with the following structure in the encrypted content:
```json
{
"res": "GFY",
"code": <gfy_code>,
"error": "<gfy_message>",
// Additional fields optionally included depending on the error type
}
```
### Expected Payloads
1. **Request Denied Warning**
- **Code**: 1
- **Payload**:
```json
{
"res": "GFY",
"code": 1,
"error": "Request Denied"
}
```
2. **Temporary Failure**
- **Code**: 2
- **Payload**:
```json
{
"res": "GFY",
"code": 2,
"error": " ... Failure"
}
```
3. **Expired Request**
- **Code**: 3
- **Payload**:
```json
{
"res": "GFY",
"code": 3,
"error": "Expired Request",
"delta": {
"max_delta_ms": 30000,
"actual_delta_ms": 45000
}
}
```
4. **Rate Limited**
- **Code**: 4
- **Payload**:
```json
{
"res": "GFY",
"code": 4,
"error": "Rate Limited",
"retry_after": 1672531199
}
```
5. **Invalid Amount**
- **Code**: 5
- **Payload**:
```json
{
"res": "GFY",
"code": 5,
"error": "Invalid Amount",
"range": {
"min": 10,
"max": 1000000
}
}
```
6. **Invalid Request**
- **Code**: 6
- **Payload**:
```json
{
"res": "GFY",
"code": 6,
"error": "Invalid Request"
}
```
Applications MUST handle these error responses gracefully and display appropriate messages to users.
## Process Flow
1. User provides their pointer directly or via NIP-05 to an application.
2. Application parses the debit pointer for the public key of the listener (user's wallet service), the relay on which it's listening, and an identifying value.
3. Application sends a debit request event (kind 2102) to the specified relay, either for a direct payment or a budget request.
4. User's wallet service receives the request and either:
a) Prompts the user to approve/deny the payment or budget, or
b) Automatically approves/denies based on predefined rules.
5. If approved:
- For direct payments: the wallet service processes the payment and adds the preimage details in the encrypted content of an ACK response.
- For budget requests: the wallet service approves the budget and sends an "ok" response.
6. Wallet service sends a response event (kind 2102) with an `e` tag referencing the original request.
* If the request is denied at step 4, the service may at it's discretion, respond with a GFY code.
## Wallet Service Behavior
Wallet services MUST:
1. Listen for kind 21002 events on specified relays.
2. Send kind 21002 response events.
3. Process payments for approved direct payment requests.
Wallet services SHOULD:
1. Prompt the user for manual approval when necessary, distinguishing between direct payments and budget requests.
2. For each application (identified by its pubkey), only consider the most recent unacknowledged request as active, replacing any previous requests from the same application.
3. Add a reasonable amount to budgets in line with outgoing fee reserve policy otherwise made known to the user.
4. Implement user-defined rules for automatic approval/denial.
## Wallet Client Behavior
Wallet clients MUST:
1. Display notifications for pending requests.
Wallet clients SHOULD:
1. Show completed request payments in transaction history.
2. Implement management of user-defined rules.
3. Promote user inspection of the event for authenticity by providing additional context known about the requesting key. (NIP-05 or WoT verification for example)
Wallet clients MAY:
1. Distinguish between direct payments and budget requests in notifications and inboxes.
2. Provide reporting functionality (ex: NIP-56) to deter abuse.
## Application Behavior
Applications MUST:
1. Obtain the user's NIP-68 debit link.
2. Send kind 21002 events to request payments or budgets.
3. Listen for kind 21002 response events.
4. Handle approvals and denials appropriately.
Applications SHOULD:
1. Provide users with the requesting key, event_id, or signature they can use to verify authenticity of the request.
2. Handle cases where unacknowledged requests are replaced by a more recent one.
3. Consider using budget requests even for one-time payments to avoid issues with invoice expiration limit or unproductive retries.
## Security Considerations
1. All content fields must be encrypted using [NIP-44](44.md).
2. Wallet services should implement strong authentication and atomic transactions to ensure that only authorized applications are granted debits and are properly constrained by user-defined budgets.
3. Users should be educated about the implications of setting auto-approval rules.
4. Wallet services should consider implementing "fail2ban" like schemes and/or NIP-56 awareness to discourage abuse.
## Handling Fluctuating Amounts and Exchange Rates
When dealing with payments or budgets that are pegged to fiat currencies or other transient assets, it's important to consider how to handle fluctuations in the Bitcoin exchange rate. This NIP recommends the following approaches:
### Client-side Handling
1. Wallet clients may allow users to set rules based not only upon satoshi amounts, but fiat currency amounts.
2. When creating a rule based on a fiat currency amount, the wallet client SHOULD:
- Log the current exchange rate based on the users quoting preferences
- Periodically check for changes in the exchange rate
- Prompt the user to update their rules if the exchange rate has changed within a certian threshold
3. Wallet clients implementing dynamic amounts MUST be clearly communicate this to the user for approval.
### Service-side Handling
1. Services MAY send new budget requests to update pricing when there are significant changes in the exchange rate.
2. When sending an updated budget request, services MUST use the same `pointer` value as the original request to associate it with the existing budget.
3. Wallet services MUST treat updated budget requests as new requests requiring user approval if the new amount exceeds what was previously auto-approved.
### Example Updated Budget Request
```json
{
"pointer": "<original_pointer_id>",
"amount_sats": 1050000, // Updated amount
"frequency": {
"number": 1,
"unit": "month"
},
"description": "Updated pricing due to exchange rate change. Previous: $50 (1000000 sats), New: $50 (1050000 sats)" // optional
}
```
By following these recommendations, both wallet clients and services can gracefully handle scenarios where payment amounts may fluctuate due to exchange rate changes, while maintaining transparency and control for the user.
## Reference Implementation
- Wallet Service: [Lightning.Pub](https://github.com/shocknet/Lightning.Pub)
- Wallet Client: [ShockWallet](https://shockwallet.app)
- Application: [demo.nip69.dev/nip68.html](https://demo.nip69.dev/nip68.html)
- Nostr-Tools (temp fork): [nostr-tools](https://github.com/shocknet/nostr-tools)