Skip to content

Latest commit

 

History

History
185 lines (152 loc) · 7.01 KB

21.md

File metadata and controls

185 lines (152 loc) · 7.01 KB

LUD-21: Currencies in payRequest.

author: lsunsi author: luizParreira author: lorenzolfm


Support for LNURL-pay currencies

This document describes an extension to the payRequest base specification that allows the WALLET to send money to a SERVICE denominating the amount in a different currency. The features proposed enable many use cases ranging from denominating an invoice in a foreign currency to a remittance-like experience.

The main features provided by this extension are:

  • SERVICE MUST inform WALLET what currencies it supports
  • WALLET MAY request an invoice with an amount denominated in one of the currencies
  • WALLET MAY request to the payment to be converted into one of the currencies

The extension is opt-in and backward compatible. Further, a supporting WALLET can always tell if a SERVICE is also supporting beforehand so the communication is never ambiguous.

Wallet-side first request

The first request is unchanged from the base specification.

Service-side first response

SERVICE must alter its JSON response to the first request to include a currencies field, as follows:

type BaseResponse = {
    tag: "payRequest",
    metadata: string,
    callback: string,
    maxSendable: number,
    minSendable: number
}

type Currency = {
  code: string, // Code of the currency, used as an ID for it. E.g.: BRL
  name: string, // Name of the currency. E.g.: Reais
  symbol: string, // Symbol of the currency. E.g.: R$
  decimals: number, // Integer; Number of decimal places. E.g.: 2
  multiplier: number, // Double; Number of millisatoshis per smallest unit of currency. E.g.: 5405.405
  convertible?: bool // Whether the payment can be converted into the currency
}

type ExtendedResponse = BaseResponse & {
  currencies: Currency[]
}
{
  "tag": "payRequest",
  "metadata": '[["text/plain","$kenu ⚡ bipa.app"]]',
  "callback": "https://api.bipa.app/ln/request/invoice/kenu",
  "maxSendable": 1000000000,
  "minSendable": 1000,
+ "currencies": [
+   {
+     "code": "BRL",
+     "name": "Real",
+     "symbol": "R$",
+     "decimals": 2,
+     "multiplier": 5404.405,
+     "convertible": true
+   }
+ ]
}
  • The inclusion of the currencies field implies the support of this extension
  • The inclusion of a currency implies it can be used for denomination of an amount
  • The inclusion of a convertible currency implies the SERVICE can quote and guarantee a price for a given currency
  • The multiplier is not guaranteed by the SERVICE and is subject to change
  • The code of a currency will be used as an identifier for the next request and must be unique
  • The code must be according to ISO-4217 if possible
  • The order of the currencies may be interpreted by the WALLET as the receiving user preference for a currency

Wallet-side second request

Upon receiving the currencies field on the response, the WALLET shows the user it has the option of denominating the amount in one of the currencies or for the payment to be creditted as a different currency for the receiver.

The inputs that must be gathered from the user are:

  • An optional denominating currency and amount (CURRENCY_D and AMOUNT_D)
  • An optional convert currency (CURRENCY_C)

The most general case has all the parameters set. It will generate an invoice with the amount equivalent to AMOUNT_D CURRENCY_D, which will be converted into CURRENCY_C by the SERVICE upon payment.

<callback><?|&>amount=<AMOUNT_D>.<CURRENCY_D>&convert=<CURRENCY_C>

Each combination of parameters is valid and generates a different use case.

  • Omitting the amount denomination implies the invoice is for millisatoshis (base spec)
  • Omitting the convert implies the receiver will get BTC from the payment, no matter the amount denomination

Note that the amount provided in all requests is always an integer number interpreted as the smallest unit of the selected currency. The smallest unit needs to be according to the decimals parameter, so the WALLET has all the needed information to receive input and show output properly.

Service-side second response

Upon receiving a currency-denominated request from WALLET, the SERVICE must return an invoice with an amount matching the converted rate for the amount in that currency. The rate used does not need to match the multiplier first informed.

If the WALLET requested an actual conversion, the SERVICE must provide an additional field alongside the invoice informing the guaranteed converted amount that will be creditted to the receiver upon payment. The converted amount, and therefore the conversion rate, must be guaranteed by the SERVICE for as long as the invoice is not expired. The converted amount must be denominated in the smallest unit of the currency, just like the amount parameter.

type BaseResponse = {
  pr: string,
  routes: [],
}

type ExtendedResponse = BaseResponse & {
  converted?: number, // Integer; Present if and only if `convert` was received.
}
{
  "pr": "lnbc1230n1pjknkl...ju36m3lyytlwv42fee8gpt6vd2v",
  "routes": [],
+ "converted": 123
}

Examples

These examples show all the possible uses of this extension by a supporting WALLET and SERVICE.

Payer queries the payee service

GET <service>/.well-known/lnurlp/<identifier>

{
  "tag": "payRequest",
  "callback": "bipa.app/callback",
  "metadata": "...",
  "minSendable": 1000,
  "maxSendable": 1000000,
  "currencies": [
    {
      "code": "BRL",
      "name": "Reais",
      "symbol": "R$",
      "decimals": 2,
      "multiplier": 5405.405,
      "convertible": true
    },
    {
      "code": "USDT",
      "name": "Tether",
      "symbol": "",
      "decimals": 6,
      "multiplier": 26315.789
    }
  ]
}
Payer sends 538 sats
// GET <callback>?amount=538000
{ "pr": "(invoice of 538 sats)" }
Payer sends 1 BRL worth of BTC
// GET <callback>?amount=100.BRL
{ "pr": "(invoice of 538 sats)" }
Payer sends 538 sats to be converted into BRL
// GET <callback>?amount=538000&convert=BRL
{ "converted": 100, "pr": "(invoice of 538 sats)" }
Payer sends 1 BRL worth of BTC to be converted into USDT
// GET <callback>?amount=100.BRL&convert=USDT
{ "converted": 200000, "pr": "(invoice of 538 sats)" }
Payer sends 1 BRL worth of BTC to unsupported service
// GET <callback>?amount=100.BRL
{ "status": "ERROR", "reason": "..." }

Related work

  • Some of the ideas included in this PR were taken from the implementation and discussion on this PR. Most precisely, @ethanrose (author) and @callebtc (contributor).

  • Some early ideas for this including some other aspects of it were hashed out (but not pull-requested) in this earlier draft too. Thanks, @luizParreira (author), @joosjager (contributor), @za-kk (contributor).