forked from nostr-protocol/nips
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add cross-currency payment methods to NWC.
This will help serve use cases like e-cash wallets, bolt-12 offers which allow for other currency denominations, and [LUD-21](lnurl/luds#251 wallet providers like UMA VASPs.
- Loading branch information
Showing
1 changed file
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,6 +75,7 @@ If the command was successful, the `error` field must be null. | |
- `QUOTA_EXCEEDED`: The wallet has exceeded its spending quota. | ||
- `RESTRICTED`: This public key is not allowed to do this operation. | ||
- `UNAUTHORIZED`: This public key has no wallet connected. | ||
- `EXPIRED`: The invoice or quote being paid has expired. | ||
- `INTERNAL`: An internal error. | ||
- `OTHER`: Other error. | ||
|
||
|
@@ -396,6 +397,17 @@ Response: | |
"block_height": 1, | ||
"block_hash": "hex string", | ||
"methods": ["pay_invoice", "get_balance", "make_invoice", "lookup_invoice", "list_transactions", "get_info"], // list of supported methods for this connection | ||
// Optional fields: | ||
"lud16": "string", // lightning address | ||
// Preferred currencies for the user. Omission of this field implies only SATs. | ||
"currencies": { | ||
"name": "string", | ||
"code": "string", | ||
"symbol": "string", | ||
"decimals": "number", | ||
"min": "number", | ||
"max": "number", | ||
}[], | ||
} | ||
} | ||
``` | ||
|
@@ -407,5 +419,297 @@ Response: | |
2. **wallet service** verifies that the author's key is authorized to perform the payment, decrypts the payload and sends the payment. | ||
3. **wallet service** responds to the event by sending an event with kind `23195` and content being a response either containing an error message or a preimage. | ||
|
||
## Cross-Currency Extensions | ||
|
||
This section describes extensions to Nostr Wallet Connect to support payments across currencies through a connected wallet. This will help serve use cases like e-cash wallets, bolt-12 offers which allow for other currency denominations, and [LUD-21](https://github.com/lnurl/luds/pull/251)-compatible wallet providers like UMA VASPs. | ||
|
||
### `lookup_user` | ||
|
||
The `lookup_user` function can be used to fetch a list of preferred currencies for a given receiver. | ||
|
||
Request: | ||
|
||
```jsonc | ||
{ | ||
"method": "lookup_user", | ||
"params": { | ||
"receiver": { | ||
// Exactly of the following fields is required: | ||
"lud16": "string|undefined", | ||
"bolt12": "string|undefined", | ||
// ... extensible to future address formats (npub, etc). | ||
}, | ||
// Optional, to retrieve FX rates for receiving currencies relative to a specific sending currency. | ||
// This currency must be supported by the sender. If omitted, SATs will be used. | ||
"base_sending_currency_code": "string|undefined", | ||
} | ||
} | ||
``` | ||
|
||
Response: | ||
|
||
```jsonc | ||
{ | ||
"result_type": "lookup_user", | ||
"result": { | ||
"receiver": { | ||
"lud16": "string", | ||
"bolt12": "string", | ||
// ... extensible to future address formats (npub, etc). | ||
}, | ||
// Contains a list of preferred currencies like LUD-21 | ||
"currencies": { | ||
"code": "string", // eg. "PHP", | ||
"name": "string", // eg. "Philippine Pesos", | ||
"symbol": "string", // eg. "₱", | ||
// Estimated number of milli-sats per smallest unit of this currency (eg. cents) | ||
// If base_sending_currency_code was specified, this is the rate relative to that currency instead of milli-sats. | ||
"multiplier": "number", | ||
// Number of digits after the decimal point for display on the sender side, and to add clarity around what the | ||
// "smallest unit" of the currency is. For example, in USD, by convention, there are 2 digits for cents - $5.95. | ||
// In this case, `decimals` would be 2. Note that the multiplier is still always in the smallest unit (cents). | ||
// In addition to display purposes, this field can be used to resolve ambiguity in what the multiplier | ||
// means. For example, if the currency is "BTC" and the multiplier is 1000, really we're exchanging in SATs, so | ||
// `decimals` would be 8. | ||
"decimals": "number", | ||
// Minimum and maximium amounts the receiver is willing/able to convert to this currency in the smallest unit of | ||
// the currency. For example, if the currency is USD, the smallest unit is cents. | ||
"min": "number", | ||
"max": "number", | ||
}[], | ||
}, | ||
} | ||
``` | ||
|
||
### `fetch_quote` | ||
|
||
The `fetch_quote` method retrieves a locked quote to send a specific amount of money to a specified receiver. This call corresponds to the `payreq` request and its response corresponds to the `converted` field in the payreq response with [LUD-21](https://github.com/lnurl/luds/pull/251). The caller must specify whether the sending or receiving currency amount is what’s being locked with this quote. For example, do I want to send exactly $5 on my side, or do I want the receiver to receive exactly 5 Pesos on the other side. This method is only required for receiver-locked sends, but is optional for sender-locked (where `pay_to_address` can be used without a quote). | ||
|
||
Request: | ||
|
||
```jsonc | ||
{ | ||
"method": "fetch_quote", | ||
"params": { | ||
"receiver": { | ||
// Exactly of the following fields is required: | ||
"lud16": "string|undefined", | ||
"bolt12": "string|undefined", | ||
// ... extensible to future address formats (npub, etc). | ||
}, | ||
"sending_currency_code": "string", | ||
"receiving_currency_code": "string", | ||
"locked_currency_side": "SENDING"|"RECEIVING", | ||
"locked_currency_amount": "number", | ||
} | ||
} | ||
``` | ||
|
||
Response: | ||
|
||
```jsonc | ||
{ | ||
"result_type": "fetch_quote", | ||
"result": { | ||
"sending_currency_code": "string", | ||
"receiving_currency_code": "string", | ||
"payment_hash": "string", // used to execute the quote | ||
"expires_at": "number", | ||
"multiplier": "number", // receiving unit per sending unit | ||
"fees": "number", // fees in the sending currency | ||
"total_receiving_amount": "number", | ||
"total_sending_amount": "number", | ||
}, | ||
} | ||
``` | ||
|
||
### `execute_quote` | ||
|
||
Sends a payment corresponding to a quote retrieved from fetch_quote. If the quote has expired, the payment will fail. | ||
|
||
Request: | ||
|
||
```jsonc | ||
{ | ||
"method": "execute_quote", | ||
"params": { | ||
"payment_hash": "string", | ||
} | ||
} | ||
``` | ||
|
||
Response: | ||
|
||
```jsonc | ||
{ | ||
"result_type": "execute_quote", | ||
"result": { | ||
"preimage": "string", | ||
}, | ||
} | ||
``` | ||
|
||
### `pay_to_address` | ||
|
||
This method directly pays the receiving user based on a fixed sending amount. The client app can complete the whole quote creation and execution exchange with this one call. Callers can optionally exclude the `receiving_currency` to allow just sending to the receiver's first preferred currency. | ||
|
||
Request: | ||
```jsonc | ||
{ | ||
"method": "pay_to_address", | ||
"params": { | ||
"receiver": { | ||
// Exactly of the following fields is required: | ||
"lud16": "string|undefined", | ||
"bolt12": "string|undefined", | ||
// ... extensible to future address formats (npub, etc). | ||
}, | ||
"sending_currency_code": "string", | ||
"receiving_currency_code": "string|undefined", | ||
"sending_currency_amount": "number", | ||
} | ||
} | ||
``` | ||
|
||
Response: | ||
```jsonc | ||
{ | ||
"result_type": "pay_to_address", | ||
"result": { | ||
"preimage": "string", | ||
"quote": { | ||
"sending_currency_code": "string", | ||
"receiving_currency_code": "string", | ||
"payment_hash": "string", | ||
"multiplier": "number", // receiving unit per sending unit | ||
"fees": "number", // fees in the sending currency | ||
"total_receiving_amount": "number", | ||
"total_sending_amount": "number", | ||
}, | ||
}, | ||
} | ||
``` | ||
|
||
### Extension of `get_balance` | ||
|
||
The `get_balance` request can take an optional `currency_code` field to specify which currency to look up. If none is specified the sats balance is returned. | ||
|
||
```jsonc | ||
{ | ||
"method": "get_balance", | ||
"params": { | ||
"currency_code": "string|undefined", | ||
} | ||
} | ||
``` | ||
|
||
Response: | ||
```jsonc | ||
{ | ||
"result_type": "get_balance", | ||
"result": { | ||
"balance": "number", // user's balance in the smallest unit of the currency | ||
"currency_code": "string" | ||
} | ||
} | ||
``` | ||
|
||
### Extension of `Invoice` and `Payment` objects | ||
|
||
The invoice/payment objects returned by `lookup_invoice` and `list_transactions` should include some new info about other currencies if applicable: | ||
|
||
```jsonc | ||
{ | ||
// ...Existing fields... | ||
|
||
// Optional field: | ||
"fx": { | ||
"receiving_currency_code": "string", | ||
"total_receiving_amount": "number", | ||
"multiplier": "number", // receiving unit per sending unit (SATs if incoming) | ||
// Remaining fields only available for outgoing payments: | ||
"sending_currency_code": "string", | ||
"fees": "number", // fees in the sending currency | ||
"total_sending_amount": "number", | ||
}, | ||
} | ||
``` | ||
|
||
### Example Cross-Currency Payments | ||
|
||
#### Directly send exactly $1 USD to a user | ||
|
||
_If you don’t care about the receiving amount or currency:_ | ||
|
||
```jsonc | ||
{ | ||
"method": "pay_to_address", | ||
"params": { | ||
"receiver": { "lud16": "[email protected]" }, | ||
"sending_currency_code": "USD", | ||
"sending_currency_amount": 100, // Denominated in ISO 4712 decimals (cents) | ||
// Can add receiving_currency_code if you want to let the sender choose | ||
} | ||
} | ||
``` | ||
|
||
_If you want to show the user how much the receiver will receive:_ | ||
|
||
```jsonc | ||
{ | ||
"method": "fetch_quote", | ||
"params": { | ||
"receiver": { "lud16": "[email protected]" }, | ||
"sending_currency_amount": "USD", | ||
"locked_currency_side": "SENDING", | ||
"locked_currency_amount": 100, // Denominated in ISO 4712 decimals (cents) | ||
// Can set receiving_currency_code if known. Otherwise, the receiver's preferred currency will be used. | ||
} | ||
} | ||
|
||
// Show the quote to the user with expiration time... | ||
|
||
// User confirms the quote and executes it | ||
{ | ||
"method": "execute_quote", | ||
"params": { | ||
"payment_hash": "hash from fetch_quote", | ||
} | ||
} | ||
``` | ||
|
||
#### Paying for some service such that the receiver receives exactly MX$5 | ||
|
||
```jsonc | ||
// First retrieve the receiver currency list if not yet known. | ||
{ | ||
"method": "lookup_user", | ||
"params": { | ||
"receiver": { "lud16": "[email protected]" }, | ||
}, | ||
} | ||
|
||
{ | ||
"method": "fetch_quote", | ||
"params": { | ||
"receiver": { "lud16": "[email protected]" }, | ||
"sending_currency_code": "USD", | ||
"receiving_currency_code": "MXN", | ||
"locked_currency_side": "RECEIVING", | ||
"locked_currency_amount": 500, // Denominated in ISO 4712 decimals (cents) | ||
}, | ||
} | ||
|
||
// Show the quote to the user with expiration time... | ||
|
||
// User confirms the quote and executes it | ||
{ | ||
"method": "execute_quote", | ||
"params": { | ||
"payment_hash": "hash from fetch_quote", | ||
} | ||
} | ||
``` | ||
|
||
## Using a dedicated relay | ||
This NIP does not specify any requirements on the type of relays used. However, if the user is using a custodial service it might make sense to use a relay that is hosted by the custodial service. The relay may then enforce authentication to prevent metadata leaks. Not depending on a 3rd party relay would also improve reliability in this case. |