From 56bbdd8ffa1b65a2918d0a0e83948953d171e94e Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Fri, 1 Dec 2023 10:22:41 -0300 Subject: [PATCH 01/12] Add proposal for currencies on payRequest --- 21.md | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 21.md diff --git a/21.md b/21.md new file mode 100644 index 0000000..f640d74 --- /dev/null +++ b/21.md @@ -0,0 +1,202 @@ +LUD-21: Currencies in `payRequest`. +================================= + +`author: lsunsi` +`author: luizParreira` +`author: lorenzolfm` + +--- + +## Support for LNURL-pay currencies + +This document describes an extension to the [payRequest](https://github.com/lnurl/luds/blob/luds/06.md) base specification that allows the `WALLET` to send money to a `SERVICE` denominating the amount in a different currencies. The main use cases supported by this extension are sending sats denominating the value in a currency with an exchange rate defined by the `SERVICE` and optionally converting the value into the currency upon payment of the invoice. Widespread implementation of this extension by broker-enabled wallets would enable a remittance-like experience for users around the globe. + +The extension is opt-in and backwards 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 callback to include a `currencies` field, as follows: + +```typescript +type BaseResponse = { + tag: "payRequest", + metadata: string, + callback: string, + maxSendable: number, + minSendable: number +}; + +type Currency = { + name: string, // Name of the currency. E.g.: Reais + symbol: string, // Symbol of the currency. E.g.: R$ + displayDecimals: number, // Number of decimal places. E.g.: 2 + price: number, // Number of currency per BTC. E.g.: 185000 + convertible?: bool // Whether the currency can be converted into. +} + +type ExtendedResponse = BaseResponse & { + currencies: { [key: string]: Currency } +} +``` + +```diff +{ + "tag": "payRequest", + "metadata": '[["text/plain","$kenu ⚡ bipa.app"]]', + "callback": "https://api.bipa.app/ln/request/invoice/kenu", + "maxSendable": 1000000000, + "minSendable": 1000, ++ "currencies": { ++ "BRL": { ++ "name": "Real", ++ "symbol": "R$", ++ "displayDecimals": 2, ++ "price": 185000, ++ "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 given currency +- The price returned on this response is not guaranteed by the `SERVICE` and is subject to change +- The `key` of a `currency` will be used as an identifier for the next request +- The `key` and other information must be according to [ISO-4217](https://en.wikipedia.org/wiki/ISO_4217) for currencies that are included in it + +### Wallet-side second request + +Upon seeing the `currencies` field on the response from `SERVICE`, a `WALLET` may show the user it has the option of denominating the amount in different currencies or even of sending sats to be credited as the different currency for the receiver. + +After gathering the input from the user, it must be encoded into the callback as query parameters as follows. + +The most general case, when the user is denominating the amount in `CURRENCY1`, but wants it to be converted to `CURRENCY2` on `SERVICE`. +`CURRENCY1` and `CURRENCY2` can be the same, which would the common use case for remittances. +```diff +- amount= ++ amount=&convert= + +In a more particular case, when user is denominating the amount in `CURRENCY`, but does not want to convert it or the service does not support conversion. +```diff +- amount= ++ amount= +``` + +In another particular case, when the user is denominating the amount in millisatoshi, but wants it to be converted to `CURRENCY` on `SERVICE`. +```diff +- amount= ++ amount=&convert= +``` + +### Service-side second response + +Upon receiving a currency denominated request from `WALLET`, the `SERVICE` must return a lightning invoice with an amount matching the converted rate from currency. The rate does not need to be the same as provided on the first response. + +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 credit to the receiver upon payment. The `converted` amount, and therefore the conversion rate, must be guaranteed by the `SERVICE` for as long as the lightning invoice is not expired. + +```typescript +type BaseResponse = { + pr: string, + routes: [], +} + +type ExtendedResponse = BaseResponse & { + converted?: number, // Present if and only if `convert` was requested. +} +``` + +```diff +{ + "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 + +```json +// GET bipa.app/.well-known/lnurlp/lsunsi + +{ + "tag": "payRequest", + "callback": "bipa.app/callback", + "metadata": "...", + "minSendable": 1000, + "maxSendable": 1000000, + "currencies": { + "BRL": { + "name": "Reais", + "symbol": "R$", + "displayDecimals": 2, + "price": 185000, + "convertible": true + }, + "USDT": { + "name": "Tether", + "symbol": "₮", + "displayDecimals": 2, + "price": 38000 + } + } +} +``` + +#### Payer sends 538 sats +```json +// GET bipa.app/callback?amount=538000 +{ + "pr": "(invoice of 538 sats)" +} +``` +#### Payer sends 538 sats as BRL +```json +// GET bipa.app/callback?amount=538000&convert=BRL +{ + "pr": "(invoice of 538 sats)", + "converted": 1 +} +``` +#### Payer sends 1 BRL worth of BTC as BTC +```json +// GET bipa.app/callback?amount=1BRL +{ + "pr": "(invoice of 538 sats)" +} +``` +#### Payer sends 1 BRL worth of BTC as BRL +```json +// GET bipa.app/callback?amount=1BRL&convert=BRL +{ + "pr": "(invoice of 538 sats)", + "converted": 1 +} +``` +#### Payer sends 1 BRL worth of BTC as USDT +```json +// GET bipa.app/callback?amount=1BRL&convert=USDT +{ + "pr": "(invoice of 538 sats)", + "converted": 0.2 +} +``` +#### Payer sends 1 BRL worth of BTC to unsupported +```json +// GET bipa.app/callback?amount=1BRL +// ERROR, because 1BRL is not a number and can't +// be interpret as milisatoshis by the unsupported service. +``` + +### Related work + +- Some of the ideas included in this PR were taken from the implementation and discussion on [this PR](https://github.com/lnurl/luds/pull/207). 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](https://github.com/bipa-app/lnurl-rfc/pull/1) too. Thanks @luizParreira (author), @joosjager (contributor), @za-kk (contributor). From 55384ff0c35027f67d8305e4dab58d13d61e35a9 Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Tue, 5 Dec 2023 15:27:48 -0300 Subject: [PATCH 02/12] Update protocol (obj -> list) (price -> multiplier) (cents) --- 21.md | 65 +++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/21.md b/21.md index f640d74..3268d18 100644 --- a/21.md +++ b/21.md @@ -31,15 +31,16 @@ type BaseResponse = { }; 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$ displayDecimals: number, // Number of decimal places. E.g.: 2 - price: number, // Number of currency per BTC. E.g.: 185000 + multiplier: number, // Number of millisatoshis per smallets unit of currency. E.g.: 5405.405 convertible?: bool // Whether the currency can be converted into. } type ExtendedResponse = BaseResponse & { - currencies: { [key: string]: Currency } + currencies: Currency[] } ``` @@ -50,15 +51,16 @@ type ExtendedResponse = BaseResponse & { "callback": "https://api.bipa.app/ln/request/invoice/kenu", "maxSendable": 1000000000, "minSendable": 1000, -+ "currencies": { -+ "BRL": { ++ "currencies": [ ++ { ++ "code": "BRL", + "name": "Real", + "symbol": "R$", + "displayDecimals": 2, -+ "price": 185000, ++ "multiplier": 5404.405, + "convertible": true + } -+ } ++ ] } ``` @@ -66,8 +68,9 @@ type ExtendedResponse = BaseResponse & { - 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 given currency - The price returned on this response is not guaranteed by the `SERVICE` and is subject to change -- The `key` of a `currency` will be used as an identifier for the next request -- The `key` and other information must be according to [ISO-4217](https://en.wikipedia.org/wiki/ISO_4217) for currencies that are included in it +- The `code` of a `currency` will be used as an identifier for the next request +- The `code` and other information must be according to [ISO-4217](https://en.wikipedia.org/wiki/ISO_4217) for currencies that are included in it +- The order of the `currencies` must be interpreted by the `WALLET` as the receiving user preference for a currency ### Wallet-side second request @@ -89,10 +92,12 @@ In a more particular case, when user is denominating the amount in `CURRENCY`, b In another particular case, when the user is denominating the amount in millisatoshi, but wants it to be converted to `CURRENCY` on `SERVICE`. ```diff -- amount= -+ amount=&convert= +- amount= ++ amount=&convert= ``` +Note that the amount provided in all requests is always in integer number denominated in the smallets possible unit of the selected currency. The smallest unit need to be um sync with the `displayDecimals` 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 a lightning invoice with an amount matching the converted rate from currency. The rate does not need to be the same as provided on the first response. @@ -132,21 +137,23 @@ These examples show all the possible uses of this extension by a supporting `WAL "metadata": "...", "minSendable": 1000, "maxSendable": 1000000, - "currencies": { - "BRL": { + "currencies": [ + { + "code": "BRL", "name": "Reais", "symbol": "R$", "displayDecimals": 2, - "price": 185000, + "multiplier": 5405.405, "convertible": true }, - "USDT": { + { + "code": "USDT", "name": "Tether", "symbol": "₮", - "displayDecimals": 2, - "price": 38000 + "displayDecimals": 6, + "multiplier": 26315.789 } - } + ] } ``` @@ -162,36 +169,36 @@ These examples show all the possible uses of this extension by a supporting `WAL // GET bipa.app/callback?amount=538000&convert=BRL { "pr": "(invoice of 538 sats)", - "converted": 1 + "converted": 100 } ``` -#### Payer sends 1 BRL worth of BTC as BTC +#### Payer sends 1 BRL (100 cents of BRL) worth of BTC as BTC ```json -// GET bipa.app/callback?amount=1BRL +// GET bipa.app/callback?amount=100BRL { "pr": "(invoice of 538 sats)" } ``` -#### Payer sends 1 BRL worth of BTC as BRL +#### Payer sends 1 BRL (100 cents of BRL) worth of BTC as BRL ```json -// GET bipa.app/callback?amount=1BRL&convert=BRL +// GET bipa.app/callback?amount=100BRL&convert=BRL { "pr": "(invoice of 538 sats)", - "converted": 1 + "converted": 100 } ``` -#### Payer sends 1 BRL worth of BTC as USDT +#### Payer sends 1 BRL (100 cents of BRL) worth of BTC as USDT (20 cents of USDT) ```json -// GET bipa.app/callback?amount=1BRL&convert=USDT +// GET bipa.app/callback?amount=100BRL&convert=USDT { "pr": "(invoice of 538 sats)", - "converted": 0.2 + "converted": 200000 } ``` -#### Payer sends 1 BRL worth of BTC to unsupported +#### Payer sends 1 BRL (100 cents of BRL) worth of BTC to unsupported ```json -// GET bipa.app/callback?amount=1BRL -// ERROR, because 1BRL is not a number and can't +// GET bipa.app/callback?amount=100BRL +// ERROR, because 100BRL is not a number and can't // be interpret as milisatoshis by the unsupported service. ``` From ab98b7d6ce1350dd4e2426eb110e7c6cf216c862 Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Tue, 5 Dec 2023 15:37:18 -0300 Subject: [PATCH 03/12] Text improvements --- 21.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/21.md b/21.md index 3268d18..93c02b5 100644 --- a/21.md +++ b/21.md @@ -96,13 +96,13 @@ In another particular case, when the user is denominating the amount in millisat + amount=&convert= ``` -Note that the amount provided in all requests is always in integer number denominated in the smallets possible unit of the selected currency. The smallest unit need to be um sync with the `displayDecimals` parameter, so the `WALLET` has all the needed information to receive input and show output properly. +Note that the amount provided in all requests is always an integer number denominated in the smallets possible unit of the selected currency. The smallest unit need to be in sync with the `displayDecimals` 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 a lightning invoice with an amount matching the converted rate from currency. The rate does not need to be the same as provided on the first response. -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 credit to the receiver upon payment. The `converted` amount, and therefore the conversion rate, must be guaranteed by the `SERVICE` for as long as the lightning invoice is not expired. +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 credit to the receiver upon payment. The `converted` amount, and therefore the conversion rate, must be guaranteed by the `SERVICE` for as long as the lightning invoice is not expired. The `converted` amount must be denominated in the smallest unit of the currency, just like the `amount` parameter. ```typescript type BaseResponse = { From 5f0ea3a2a3fd5b840fd6c2ef0f16d5be5db6c366 Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Tue, 5 Dec 2023 16:42:09 -0300 Subject: [PATCH 04/12] Replace display decimals for decimals to denote it's importance --- 21.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/21.md b/21.md index 93c02b5..442dac5 100644 --- a/21.md +++ b/21.md @@ -34,7 +34,7 @@ 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$ - displayDecimals: number, // Number of decimal places. E.g.: 2 + decimals: number, // Number of decimal places. E.g.: 2 multiplier: number, // Number of millisatoshis per smallets unit of currency. E.g.: 5405.405 convertible?: bool // Whether the currency can be converted into. } @@ -56,7 +56,7 @@ type ExtendedResponse = BaseResponse & { + "code": "BRL", + "name": "Real", + "symbol": "R$", -+ "displayDecimals": 2, ++ "decimals": 2, + "multiplier": 5404.405, + "convertible": true + } @@ -96,7 +96,7 @@ In another particular case, when the user is denominating the amount in millisat + amount=&convert= ``` -Note that the amount provided in all requests is always an integer number denominated in the smallets possible unit of the selected currency. The smallest unit need to be in sync with the `displayDecimals` parameter, so the `WALLET` has all the needed information to receive input and show output properly. +Note that the amount provided in all requests is always an integer number denominated in the smallets possible unit of the selected currency. The smallest unit need to be in sync with the `decimals` parameter, so the `WALLET` has all the needed information to receive input and show output properly. ### Service-side second response @@ -142,7 +142,7 @@ These examples show all the possible uses of this extension by a supporting `WAL "code": "BRL", "name": "Reais", "symbol": "R$", - "displayDecimals": 2, + "decimals": 2, "multiplier": 5405.405, "convertible": true }, @@ -150,7 +150,7 @@ These examples show all the possible uses of this extension by a supporting `WAL "code": "USDT", "name": "Tether", "symbol": "₮", - "displayDecimals": 6, + "decimals": 6, "multiplier": 26315.789 } ] From 7bf8f3afadc4b6b4e2ddd0f6b0acb2a4871b7e49 Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Wed, 6 Dec 2023 11:43:33 -0300 Subject: [PATCH 05/12] Update spec adding separator to amount with currency --- 21.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/21.md b/21.md index 442dac5..61de1e7 100644 --- a/21.md +++ b/21.md @@ -82,12 +82,12 @@ The most general case, when the user is denominating the amount in `CURRENCY1`, `CURRENCY1` and `CURRENCY2` can be the same, which would the common use case for remittances. ```diff - amount= -+ amount=&convert= ++ amount=.&convert= In a more particular case, when user is denominating the amount in `CURRENCY`, but does not want to convert it or the service does not support conversion. ```diff - amount= -+ amount= ++ amount=. ``` In another particular case, when the user is denominating the amount in millisatoshi, but wants it to be converted to `CURRENCY` on `SERVICE`. @@ -174,14 +174,14 @@ These examples show all the possible uses of this extension by a supporting `WAL ``` #### Payer sends 1 BRL (100 cents of BRL) worth of BTC as BTC ```json -// GET bipa.app/callback?amount=100BRL +// GET bipa.app/callback?amount=100.BRL { "pr": "(invoice of 538 sats)" } ``` #### Payer sends 1 BRL (100 cents of BRL) worth of BTC as BRL ```json -// GET bipa.app/callback?amount=100BRL&convert=BRL +// GET bipa.app/callback?amount=100.BRL&convert=BRL { "pr": "(invoice of 538 sats)", "converted": 100 @@ -189,7 +189,7 @@ These examples show all the possible uses of this extension by a supporting `WAL ``` #### Payer sends 1 BRL (100 cents of BRL) worth of BTC as USDT (20 cents of USDT) ```json -// GET bipa.app/callback?amount=100BRL&convert=USDT +// GET bipa.app/callback?amount=100.BRL&convert=USDT { "pr": "(invoice of 538 sats)", "converted": 200000 @@ -197,7 +197,7 @@ These examples show all the possible uses of this extension by a supporting `WAL ``` #### Payer sends 1 BRL (100 cents of BRL) worth of BTC to unsupported ```json -// GET bipa.app/callback?amount=100BRL +// GET bipa.app/callback?amount=100.BRL // ERROR, because 100BRL is not a number and can't // be interpret as milisatoshis by the unsupported service. ``` From 2d37cec01769cbd0812aeae06919bac0104f8bc0 Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Wed, 6 Dec 2023 14:05:06 -0300 Subject: [PATCH 06/12] Update spec to include source/target about currencies --- 21.md | 139 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 70 insertions(+), 69 deletions(-) diff --git a/21.md b/21.md index 61de1e7..2b784a8 100644 --- a/21.md +++ b/21.md @@ -9,7 +9,13 @@ LUD-21: Currencies in `payRequest`. ## Support for LNURL-pay currencies -This document describes an extension to the [payRequest](https://github.com/lnurl/luds/blob/luds/06.md) base specification that allows the `WALLET` to send money to a `SERVICE` denominating the amount in a different currencies. The main use cases supported by this extension are sending sats denominating the value in a currency with an exchange rate defined by the `SERVICE` and optionally converting the value into the currency upon payment of the invoice. Widespread implementation of this extension by broker-enabled wallets would enable a remittance-like experience for users around the globe. +This document describes an extension to the [payRequest](https://github.com/lnurl/luds/blob/luds/06.md) 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 amount denominated in one of the currencies +- `WALLET` **MAY** request to the payment to be converted into one of the currencies +- `WALLET` **MAY** inform the `SERVICE` the amount and currency used the buy the amount being paid The extension is opt-in and backwards compatible. Further, a supporting `WALLET` can always tell if a `SERVICE` is also supporting beforehand so the communication is never ambiguous. @@ -28,14 +34,14 @@ type BaseResponse = { 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, // Number of decimal places. E.g.: 2 - multiplier: number, // Number of millisatoshis per smallets unit of currency. E.g.: 5405.405 + decimals: number, // Integer; Number of decimal places. E.g.: 2 + multiplier: number, // Double; Number of millisatoshis per smallets unit of currency. E.g.: 5405.405 convertible?: bool // Whether the currency can be converted into. } @@ -67,42 +73,39 @@ type ExtendedResponse = BaseResponse & { - 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 given currency -- The price returned on this response 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 +- The `multiplier` returned on this response 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` and other information must be according to [ISO-4217](https://en.wikipedia.org/wiki/ISO_4217) for currencies that are included in it - The order of the `currencies` must be interpreted by the `WALLET` as the receiving user preference for a currency ### Wallet-side second request -Upon seeing the `currencies` field on the response from `SERVICE`, a `WALLET` may show the user it has the option of denominating the amount in different currencies or even of sending sats to be credited as the different currency for the receiver. +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 credit as the different currency for the receiver. If the BTC being used for the payment is was bought as part of the payment, the `WALLET` may inform the `SERVICE` about it's cost. -After gathering the input from the user, it must be encoded into the callback as query parameters as follows. +The inputs that must be gathered from the user are: +- An optional denominating currency and amount (`CURRENCY_D` and `AMOUNT_D`) +- An optional source currency and amount (`CURRENCY_S` and `AMOUNT_S`) +- An optional target currency (`CURRENCY_T`) -The most general case, when the user is denominating the amount in `CURRENCY1`, but wants it to be converted to `CURRENCY2` on `SERVICE`. -`CURRENCY1` and `CURRENCY2` can be the same, which would the common use case for remittances. -```diff -- amount= -+ amount=.&convert= +The most general case has all the possible parameters present. +It will generate an invoice with amount equivalent to `AMOUNT_D` `CURRENCY_D`, that will be converted into `CURRENCY_T` by the `SERVICE` upon payment, which will be done with BTC that costed the sender `AMOUNT_S` `CURRENCY_S`. -In a more particular case, when user is denominating the amount in `CURRENCY`, but does not want to convert it or the service does not support conversion. -```diff -- amount= -+ amount=. -``` +`amount=.&source=.&target=` -In another particular case, when the user is denominating the amount in millisatoshi, but wants it to be converted to `CURRENCY` on `SERVICE`. -```diff -- amount= -+ amount=&convert= -``` +Each combination of parameters is valid and generates a different use case. +- Ommiting the `amount` denomination implies the invoice is for millisatoshis (base spec) +- Ommiting the `source` prevents the receiver from knowing the cost of the BTC being sent +- Ommiting the `target` implies the receiver will get BTC from the payment, no matter the `source` or `amount` denomination -Note that the amount provided in all requests is always an integer number denominated in the smallets possible unit of the selected currency. The smallest unit need to be in sync with the `decimals` parameter, so the `WALLET` has all the needed information to receive input and show output properly. +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 a lightning invoice with an amount matching the converted rate from currency. The rate does not need to be the same as provided on the first 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 credit 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. -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 credit to the receiver upon payment. The `converted` amount, and therefore the conversion rate, must be guaranteed by the `SERVICE` for as long as the lightning invoice is not expired. The `converted` amount must be denominated in the smallest unit of the currency, just like the `amount` parameter. +If the `WALLET` informed the `SERVICE` about the cost of the BTC used in the payment, the `SERVICE` may use the information to display currency-to-currency exchange rates. ```typescript type BaseResponse = { @@ -111,7 +114,7 @@ type BaseResponse = { } type ExtendedResponse = BaseResponse & { - converted?: number, // Present if and only if `convert` was requested. + converted?: number, // Integer; Present if and only if `target` was received. } ``` @@ -127,10 +130,8 @@ type ExtendedResponse = BaseResponse & { These examples show all the possible uses of this extension by a supporting `WALLET` and `SERVICE`. #### Payer queries the payee service - +`GET /.well-known/lnurlp/` ```json -// GET bipa.app/.well-known/lnurlp/lsunsi - { "tag": "payRequest", "callback": "bipa.app/callback", @@ -156,50 +157,50 @@ These examples show all the possible uses of this extension by a supporting `WAL ] } ``` - -#### Payer sends 538 sats -```json -// GET bipa.app/callback?amount=538000 -{ - "pr": "(invoice of 538 sats)" -} +###### Payer sends 538 sats +```json5 +// GET ?amount=538000 +{ "pr": "(invoice of 538 sats)" } ``` -#### Payer sends 538 sats as BRL -```json -// GET bipa.app/callback?amount=538000&convert=BRL -{ - "pr": "(invoice of 538 sats)", - "converted": 100 -} +###### Payer sends 1 BRL worth of BTC +```json5 +// GET ?amount=100.BRL +{ "pr": "(invoice of 538 sats)" } ``` -#### Payer sends 1 BRL (100 cents of BRL) worth of BTC as BTC -```json -// GET bipa.app/callback?amount=100.BRL -{ - "pr": "(invoice of 538 sats)" -} +###### Payer sends 538 sats to be converted into BRL +```json5 +// GET ?amount=538000&target=BRL +{ "converted": 100, "pr": "(invoice of 538 sats)" } ``` -#### Payer sends 1 BRL (100 cents of BRL) worth of BTC as BRL -```json -// GET bipa.app/callback?amount=100.BRL&convert=BRL -{ - "pr": "(invoice of 538 sats)", - "converted": 100 -} +###### Payer sends 538 sats that costed 0.2 USDT +```json5 +// GET ?amount=538000&source=200000.USDT +{ "pr": "(invoice of 538 sats)" } ``` -#### Payer sends 1 BRL (100 cents of BRL) worth of BTC as USDT (20 cents of USDT) -```json -// GET bipa.app/callback?amount=100.BRL&convert=USDT -{ - "pr": "(invoice of 538 sats)", - "converted": 200000 -} +###### Payer sends 1 BRL worth of BTC to be converted into USDT +```json5 +// GET ?amount=100.BRL&target=USDT +{ "converted": 200000, "pr": "(invoice of 538 sats)" } ``` -#### Payer sends 1 BRL (100 cents of BRL) worth of BTC to unsupported -```json -// GET bipa.app/callback?amount=100.BRL -// ERROR, because 100BRL is not a number and can't -// be interpret as milisatoshis by the unsupported service. +###### Payer sends 1 BRL worth of BTC that costed 0.2 USDT +```json5 +// GET ?amount=100.BRL&source=200000.USDT +{ "pr": "(invoice of 538 sats)" } +``` +###### Payer sends 538 sats that costed 0.2 USDT to be converted into BRL +```json5 +// GET ?amount=538000&source=200000.USDT&target=BRL +{ "converted": 100, "pr": "(invoice of 538 sats)" } +``` +###### Payer sends 1 BRL worth of BTC that costed 0.2 USDT to converted into BRL +```json5 +// GET ?amount=100.BRL&source=200000.USDT&target=BRL +{ "converted": 100, "pr": "(invoice of 538 sats)" } +``` +###### Payer sends 1 BRL worth of BTC to unsupported service +```json5 +// GET ?amount=100.BRL +{ "status": "ERROR", "reason": "..." } ``` ### Related work From 721e6429d44e4497a24f96fd5e63bbdf85cb5e43 Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Wed, 6 Dec 2023 14:56:44 -0300 Subject: [PATCH 07/12] Fix english spelling with grammarly's help --- 21.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/21.md b/21.md index 2b784a8..ad8df91 100644 --- a/21.md +++ b/21.md @@ -13,11 +13,11 @@ This document describes an extension to the [payRequest](https://github.com/lnur The main features provided by this extension are: - `SERVICE` **MUST** inform `WALLET` what currencies it supports -- `WALLET` **MAY** request an invoice with amount denominated in one of the currencies +- `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 -- `WALLET` **MAY** inform the `SERVICE` the amount and currency used the buy the amount being paid +- `WALLET` **MAY** inform the `SERVICE` of the amount and currency used to buy the amount being paid -The extension is opt-in and backwards compatible. Further, a supporting `WALLET` can always tell if a `SERVICE` is also supporting beforehand so the communication is never ambiguous. +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 @@ -25,7 +25,7 @@ The first request is unchanged from the base specification. ### Service-side first response -`SERVICE` must alter its JSON response to the first callback to include a `currencies` field, as follows: +`SERVICE` must alter its JSON response to the first request to include a `currencies` field, as follows: ```typescript type BaseResponse = { @@ -41,8 +41,8 @@ type Currency = { 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 smallets unit of currency. E.g.: 5405.405 - convertible?: bool // Whether the currency can be converted into. + 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 & { @@ -72,15 +72,15 @@ type ExtendedResponse = BaseResponse & { - 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 given currency -- The `multiplier` returned on this response is not guaranteed by the `SERVICE` and is subject to change +- 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` and other information must be according to [ISO-4217](https://en.wikipedia.org/wiki/ISO_4217) for currencies that are included in it -- The order of the `currencies` must be interpreted by the `WALLET` as the receiving user preference for a currency +- The `code` must be according to [ISO-4217](https://en.wikipedia.org/wiki/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 credit as the different currency for the receiver. If the BTC being used for the payment is was bought as part of the payment, the `WALLET` may inform the `SERVICE` about it's cost. +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. If the BTC being used for the payment is was bought as part of the payment, the `WALLET` may inform the `SERVICE` about it's cost. The inputs that must be gathered from the user are: - An optional denominating currency and amount (`CURRENCY_D` and `AMOUNT_D`) @@ -88,22 +88,22 @@ The inputs that must be gathered from the user are: - An optional target currency (`CURRENCY_T`) The most general case has all the possible parameters present. -It will generate an invoice with amount equivalent to `AMOUNT_D` `CURRENCY_D`, that will be converted into `CURRENCY_T` by the `SERVICE` upon payment, which will be done with BTC that costed the sender `AMOUNT_S` `CURRENCY_S`. +It will generate an invoice with the amount equivalent to `AMOUNT_D` `CURRENCY_D`, which will be converted into `CURRENCY_T` by the `SERVICE` upon payment, which will be done with BTC that costs the sender `AMOUNT_S` `CURRENCY_S`. `amount=.&source=.&target=` Each combination of parameters is valid and generates a different use case. -- Ommiting the `amount` denomination implies the invoice is for millisatoshis (base spec) -- Ommiting the `source` prevents the receiver from knowing the cost of the BTC being sent -- Ommiting the `target` implies the receiver will get BTC from the payment, no matter the `source` or `amount` denomination +- Omitting the `amount` denomination implies the invoice is for millisatoshis (base spec) +- Omitting the `source` prevents the receiver from knowing the cost of the BTC being sent +- Omitting the `target` implies the receiver will get BTC from the payment, no matter the `source` or `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. +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 credit 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. +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. If the `WALLET` informed the `SERVICE` about the cost of the BTC used in the payment, the `SERVICE` may use the information to display currency-to-currency exchange rates. @@ -172,7 +172,7 @@ These examples show all the possible uses of this extension by a supporting `WAL // GET ?amount=538000&target=BRL { "converted": 100, "pr": "(invoice of 538 sats)" } ``` -###### Payer sends 538 sats that costed 0.2 USDT +###### Payer sends 538 sats that cost 0.2 USDT ```json5 // GET ?amount=538000&source=200000.USDT { "pr": "(invoice of 538 sats)" } @@ -182,17 +182,17 @@ These examples show all the possible uses of this extension by a supporting `WAL // GET ?amount=100.BRL&target=USDT { "converted": 200000, "pr": "(invoice of 538 sats)" } ``` -###### Payer sends 1 BRL worth of BTC that costed 0.2 USDT +###### Payer sends 1 BRL worth of BTC that cost 0.2 USDT ```json5 // GET ?amount=100.BRL&source=200000.USDT { "pr": "(invoice of 538 sats)" } ``` -###### Payer sends 538 sats that costed 0.2 USDT to be converted into BRL +###### Payer sends 538 sats that cost 0.2 USDT to be converted into BRL ```json5 // GET ?amount=538000&source=200000.USDT&target=BRL { "converted": 100, "pr": "(invoice of 538 sats)" } ``` -###### Payer sends 1 BRL worth of BTC that costed 0.2 USDT to converted into BRL +###### Payer sends 1 BRL worth of BTC that cost 0.2 USDT to converted into BRL ```json5 // GET ?amount=100.BRL&source=200000.USDT&target=BRL { "converted": 100, "pr": "(invoice of 538 sats)" } @@ -207,4 +207,4 @@ These examples show all the possible uses of this extension by a supporting `WAL - Some of the ideas included in this PR were taken from the implementation and discussion on [this PR](https://github.com/lnurl/luds/pull/207). 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](https://github.com/bipa-app/lnurl-rfc/pull/1) too. Thanks @luizParreira (author), @joosjager (contributor), @za-kk (contributor). +- Some early ideas for this including some other aspects of it were hashed out (but not pull-requested) in this [earlier draft](https://github.com/bipa-app/lnurl-rfc/pull/1) too. Thanks, @luizParreira (author), @joosjager (contributor), @za-kk (contributor). From 1d669fcc16b5d03be258143e3abf1c0e0d722e60 Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Thu, 14 Dec 2023 15:50:15 -0300 Subject: [PATCH 08/12] Rename source to cost and target to convert --- 21.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/21.md b/21.md index ad8df91..56e72c3 100644 --- a/21.md +++ b/21.md @@ -84,18 +84,18 @@ Upon receiving the `currencies` field on the response, the `WALLET` shows the us The inputs that must be gathered from the user are: - An optional denominating currency and amount (`CURRENCY_D` and `AMOUNT_D`) -- An optional source currency and amount (`CURRENCY_S` and `AMOUNT_S`) -- An optional target currency (`CURRENCY_T`) +- An optional `cost` currency and amount (`CURRENCY_S` and `AMOUNT_S`) +- An optional `convert` currency (`CURRENCY_T`) The most general case has all the possible parameters present. It will generate an invoice with the amount equivalent to `AMOUNT_D` `CURRENCY_D`, which will be converted into `CURRENCY_T` by the `SERVICE` upon payment, which will be done with BTC that costs the sender `AMOUNT_S` `CURRENCY_S`. -`amount=.&source=.&target=` +`amount=.&cost=.&convert=` 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 `source` prevents the receiver from knowing the cost of the BTC being sent -- Omitting the `target` implies the receiver will get BTC from the payment, no matter the `source` or `amount` denomination +- Omitting the `cost` prevents the receiver from knowing the cost of the BTC being sent +- Omitting the `convert` implies the receiver will get BTC from the payment, no matter the `cost` or `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. @@ -114,7 +114,7 @@ type BaseResponse = { } type ExtendedResponse = BaseResponse & { - converted?: number, // Integer; Present if and only if `target` was received. + converted?: number, // Integer; Present if and only if `convert` was received. } ``` @@ -169,32 +169,32 @@ These examples show all the possible uses of this extension by a supporting `WAL ``` ###### Payer sends 538 sats to be converted into BRL ```json5 -// GET ?amount=538000&target=BRL +// GET ?amount=538000&convert=BRL { "converted": 100, "pr": "(invoice of 538 sats)" } ``` ###### Payer sends 538 sats that cost 0.2 USDT ```json5 -// GET ?amount=538000&source=200000.USDT +// GET ?amount=538000&cost=200000.USDT { "pr": "(invoice of 538 sats)" } ``` ###### Payer sends 1 BRL worth of BTC to be converted into USDT ```json5 -// GET ?amount=100.BRL&target=USDT +// GET ?amount=100.BRL&convert=USDT { "converted": 200000, "pr": "(invoice of 538 sats)" } ``` ###### Payer sends 1 BRL worth of BTC that cost 0.2 USDT ```json5 -// GET ?amount=100.BRL&source=200000.USDT +// GET ?amount=100.BRL&cost=200000.USDT { "pr": "(invoice of 538 sats)" } ``` ###### Payer sends 538 sats that cost 0.2 USDT to be converted into BRL ```json5 -// GET ?amount=538000&source=200000.USDT&target=BRL +// GET ?amount=538000&cost=200000.USDT&convert=BRL { "converted": 100, "pr": "(invoice of 538 sats)" } ``` ###### Payer sends 1 BRL worth of BTC that cost 0.2 USDT to converted into BRL ```json5 -// GET ?amount=100.BRL&source=200000.USDT&target=BRL +// GET ?amount=100.BRL&cost=200000.USDT&convert=BRL { "converted": 100, "pr": "(invoice of 538 sats)" } ``` ###### Payer sends 1 BRL worth of BTC to unsupported service From 8580e3c8cbfd8fc95a6c0e5f7fcb5b048a0d5b61 Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Fri, 15 Dec 2023 14:01:53 -0300 Subject: [PATCH 09/12] Remove cost field (to be added in other LUD) --- 21.md | 37 ++++++------------------------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/21.md b/21.md index 56e72c3..476ec1c 100644 --- a/21.md +++ b/21.md @@ -15,7 +15,6 @@ 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 -- `WALLET` **MAY** inform the `SERVICE` of the amount and currency used to buy the amount being paid 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. @@ -80,22 +79,20 @@ type ExtendedResponse = BaseResponse & { ### 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. If the BTC being used for the payment is was bought as part of the payment, the `WALLET` may inform the `SERVICE` about it's cost. +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 `cost` currency and amount (`CURRENCY_S` and `AMOUNT_S`) -- An optional `convert` currency (`CURRENCY_T`) +- An optional `convert` currency (`CURRENCY_C`) -The most general case has all the possible parameters present. -It will generate an invoice with the amount equivalent to `AMOUNT_D` `CURRENCY_D`, which will be converted into `CURRENCY_T` by the `SERVICE` upon payment, which will be done with BTC that costs the sender `AMOUNT_S` `CURRENCY_S`. +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. -`amount=.&cost=.&convert=` +`amount=.&convert=` 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 `cost` prevents the receiver from knowing the cost of the BTC being sent -- Omitting the `convert` implies the receiver will get BTC from the payment, no matter the `cost` or `amount` denomination +- 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. @@ -105,8 +102,6 @@ Upon receiving a currency-denominated request from `WALLET`, the `SERVICE` must 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. -If the `WALLET` informed the `SERVICE` about the cost of the BTC used in the payment, the `SERVICE` may use the information to display currency-to-currency exchange rates. - ```typescript type BaseResponse = { pr: string, @@ -172,31 +167,11 @@ These examples show all the possible uses of this extension by a supporting `WAL // GET ?amount=538000&convert=BRL { "converted": 100, "pr": "(invoice of 538 sats)" } ``` -###### Payer sends 538 sats that cost 0.2 USDT -```json5 -// GET ?amount=538000&cost=200000.USDT -{ "pr": "(invoice of 538 sats)" } -``` ###### Payer sends 1 BRL worth of BTC to be converted into USDT ```json5 // GET ?amount=100.BRL&convert=USDT { "converted": 200000, "pr": "(invoice of 538 sats)" } ``` -###### Payer sends 1 BRL worth of BTC that cost 0.2 USDT -```json5 -// GET ?amount=100.BRL&cost=200000.USDT -{ "pr": "(invoice of 538 sats)" } -``` -###### Payer sends 538 sats that cost 0.2 USDT to be converted into BRL -```json5 -// GET ?amount=538000&cost=200000.USDT&convert=BRL -{ "converted": 100, "pr": "(invoice of 538 sats)" } -``` -###### Payer sends 1 BRL worth of BTC that cost 0.2 USDT to converted into BRL -```json5 -// GET ?amount=100.BRL&cost=200000.USDT&convert=BRL -{ "converted": 100, "pr": "(invoice of 538 sats)" } -``` ###### Payer sends 1 BRL worth of BTC to unsupported service ```json5 // GET ?amount=100.BRL From b5cf0421c45e876e2c0f129663cb262d11c2454b Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Wed, 27 Dec 2023 07:33:37 -0300 Subject: [PATCH 10/12] add convertible max/min sendable fields --- 21.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/21.md b/21.md index 476ec1c..6601263 100644 --- a/21.md +++ b/21.md @@ -41,7 +41,10 @@ type Currency = { 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 + convertible?: { // Whether the payment can be converted into the currency + max: number, // Integer; Max converted amount of currency + min: number // Integer; Min converted amount of currency + } } type ExtendedResponse = BaseResponse & { @@ -63,7 +66,10 @@ type ExtendedResponse = BaseResponse & { + "symbol": "R$", + "decimals": 2, + "multiplier": 5404.405, -+ "convertible": true ++ "convertible": { ++ "max": 100000, ++ "min": 1000 ++ } + } + ] } @@ -71,11 +77,12 @@ type ExtendedResponse = BaseResponse & { - 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 inclusion of a `convertible` field 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](https://en.wikipedia.org/wiki/ISO_4217) if possible - The order of the `currencies` may be interpreted by the `WALLET` as the receiving user preference for a currency +- The `max` and `min` fields within `convertible` field must be respected by `WALLET` on `convertible` requests ### Wallet-side second request @@ -140,7 +147,10 @@ These examples show all the possible uses of this extension by a supporting `WAL "symbol": "R$", "decimals": 2, "multiplier": 5405.405, - "convertible": true + "convertible": { + "max": 100000, + "min": 100 + } }, { "code": "USDT", From 6a5b6e029f7716a6a6caec75b8b2521fef29d148 Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Wed, 27 Dec 2023 16:07:38 -0300 Subject: [PATCH 11/12] Add note for large decimals --- 21.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/21.md b/21.md index 6601263..187633e 100644 --- a/21.md +++ b/21.md @@ -188,6 +188,12 @@ These examples show all the possible uses of this extension by a supporting `WAL { "status": "ERROR", "reason": "..." } ``` +### Note for large decimals + +If the `decimals` of a currency is too large, it's smallest unit will be very small. That means that the `amount` passed to the callback will be a huge integer and it may not fit in reasonable integer implementations (32 or 64 bits). In this case, it's sensible for `SERVICE` to use a smaller than maximum decimals in order to avoid compatibility issues. + +For example, DAI has 18 decimal places, so sending 20 DAI would imply an `amount` of `20000000000000000000.DAI`, which is what we want to avoid. Having `decimals` set to 8 for example, would better fit this extension. + ### Related work - Some of the ideas included in this PR were taken from the implementation and discussion on [this PR](https://github.com/lnurl/luds/pull/207). Most precisely, @ethanrose (author) and @callebtc (contributor). From f70a0d673de068ed5cffae362d8ba0aa0ae21259 Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Thu, 28 Dec 2023 09:11:55 -0300 Subject: [PATCH 12/12] Make converted structure richer --- 21.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/21.md b/21.md index 187633e..3113db4 100644 --- a/21.md +++ b/21.md @@ -107,7 +107,16 @@ Note that the amount provided in all requests is always an integer number interp 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. +If the `WALLET` requested an actual conversion, the `SERVICE` must provide an additional `converted` 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 on the callback. + +Alongside the `amount` in the `converted` object, the `SERVICE` must also inform how many millisatoshis will be taken as `fee` for the conversion. Finally, a new `multiplier` in the same form as on the first request, must also be present. + +The following restriction **must** be met: +> invoice amount msat = `amount` * `multiplier` + `fee` + +This criteria implies the `amount` must be in the smallest unit of currency and net of `fee`. +The `fee` must be in millisatoshis and should be converted into millisatoshis if taken in other currencies. +The `multiplier` will act as final price net of fee of the conversion. ```typescript type BaseResponse = { @@ -116,7 +125,11 @@ type BaseResponse = { } type ExtendedResponse = BaseResponse & { - converted?: number, // Integer; Present if and only if `convert` was received. + converted?: { + multiplier: number, // Double; The quoted multiplier of the conversion. + amount: number, // Integer; Number of currency smallest units the payer will receive after fee. + fee: number // Integer; Number of millisatoshis representing the fee taken for the conversion. + } } ``` @@ -124,7 +137,11 @@ type ExtendedResponse = BaseResponse & { { "pr": "lnbc1230n1pjknkl...ju36m3lyytlwv42fee8gpt6vd2v", "routes": [], -+ "converted": 123 ++ "converted": { ++ "multiplier": 4321.123, ++ "amount": 123, ++ "fee": 1 ++ } } ``` @@ -175,12 +192,12 @@ These examples show all the possible uses of this extension by a supporting `WAL ###### Payer sends 538 sats to be converted into BRL ```json5 // GET ?amount=538000&convert=BRL -{ "converted": 100, "pr": "(invoice of 538 sats)" } +{ "pr": "(invoice of 538 sats)", "converted": { "amount": 100, "fee": 1000, "multiplier": 5370 } } ``` ###### Payer sends 1 BRL worth of BTC to be converted into USDT ```json5 // GET ?amount=100.BRL&convert=USDT -{ "converted": 200000, "pr": "(invoice of 538 sats)" } +{ "pr": "(invoice of 538 sats)", "converted": { "amount": 200000, "fee": 2000, "multiplier": 2.68 } } ``` ###### Payer sends 1 BRL worth of BTC to unsupported service ```json5