mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-12-23 00:45:53 -05:00
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](https://github.com/lnurl/luds/pull/251)-compatible wallet providers like UMA VASPs.
This commit is contained in:
parent
8c47577ecb
commit
56c4d0ff7d
304
47.md
304
47.md
|
@ -75,6 +75,7 @@ If the command was successful, the `error` field must be null.
|
||||||
- `QUOTA_EXCEEDED`: The wallet has exceeded its spending quota.
|
- `QUOTA_EXCEEDED`: The wallet has exceeded its spending quota.
|
||||||
- `RESTRICTED`: This public key is not allowed to do this operation.
|
- `RESTRICTED`: This public key is not allowed to do this operation.
|
||||||
- `UNAUTHORIZED`: This public key has no wallet connected.
|
- `UNAUTHORIZED`: This public key has no wallet connected.
|
||||||
|
- `EXPIRED`: The invoice or quote being paid has expired.
|
||||||
- `INTERNAL`: An internal error.
|
- `INTERNAL`: An internal error.
|
||||||
- `OTHER`: Other error.
|
- `OTHER`: Other error.
|
||||||
|
|
||||||
|
@ -396,6 +397,17 @@ Response:
|
||||||
"block_height": 1,
|
"block_height": 1,
|
||||||
"block_hash": "hex string",
|
"block_hash": "hex string",
|
||||||
"methods": ["pay_invoice", "get_balance", "make_invoice", "lookup_invoice", "list_transactions", "get_info"], // list of supported methods for this connection
|
"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.
|
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.
|
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": "$alice@vasp.net" },
|
||||||
|
"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": "$alice@vasp.net" },
|
||||||
|
"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": "$alice@vasp.net" },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"method": "fetch_quote",
|
||||||
|
"params": {
|
||||||
|
"receiver": { "lud16": "$alice@vasp.net" },
|
||||||
|
"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
|
## 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.
|
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.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user