Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LUD-21: Specify a payment amount in local unit of account #207

Open
wants to merge 7 commits into
base: luds
Choose a base branch
from

Conversation

ethanrose
Copy link
Contributor

@ethanrose ethanrose commented Mar 16, 2023

The idea here is that the recipient needs to receive an exact amount of fiat currency. Typically this is for a point-of-sale use-case where a lightning service is acting as a bitcoin-to-fiat payment processor.

Currently, a sender would have to use their own calculator to determine how many sats to pay, and the recipient inevitably would receive the wrong amount. This is a massive hinderance to merchant adoption of lightning.

@ethanrose ethanrose changed the title LUD-21: Guarantee payment amount in a localized currency using `payRe… LUD-21: Guarantee payment amount in a localized currency using payRequest. Mar 16, 2023
@ethanrose
Copy link
Contributor Author

I've also put the 06 -> xx range in the dotfile into numerical order so that they are easier to read. Maybe there's a reason they were out of order that I didn't know about 😬

NOTE: I haven't regenerated the .png from the dot file yet, if anyone wants to commit that please feel free (or has suggestions for software that I can use on a mac to do it!)

@ethanrose
Copy link
Contributor Author

ethanrose commented Mar 16, 2023

Imagine a merchant in the Philippines has a physical LNURL-Pay QR code at the grocery store checkout, and it looks like this! SUPER! They can accept payments from bitcoiners! BUT they need to receive an exact amount of Philippine Pesos, which LNURL doesn't support 😭 (yet).

So the bitcoiner uses their web browser to figure out how many sats to send to pay their 100 pesos -- but once their 8230 sats arrive it gets converted to 99.42 pesos 😭

image

We can fix this problem with a new LNURL spec!

@ethanrose ethanrose changed the title LUD-21: Guarantee payment amount in a localized currency using payRequest. LUD-21: Specify payment amount in recipient's own currency Mar 16, 2023
@ethanrose ethanrose changed the title LUD-21: Specify payment amount in recipient's own currency LUD-21: Specify a payment amount in recipient's own currency Mar 16, 2023
@fiatjaf
Copy link
Collaborator

fiatjaf commented Mar 16, 2023

This is great, thank you for pushing the true lnurl-pay vision on Philippines!

One problem is: what if the wallet doesn't support the currency parameter? Can we make so it will still work, even if the UX is awful? For example, the service can return a range of minSendable/maxSendable and then the merchant can tell me what amount to select on my own wallet when I am trying to pay.

@ethanrose
Copy link
Contributor Author

ethanrose commented Mar 16, 2023

Hey Jaf!! We’ve actually implemented it already (because we wanted to use it within our own wallet anyways) and it DOES NOT break existing wallets thankfully!

Here’s a live example of our payRequest details response:
https://app.pouch.ph/.well-known/lnurlp/ethan

We could train our cashiers here to compute the “sats” amount to send but it’s still subject to the volatility in the 30 seconds between computing and settling and it’s a large training barrier to onboarding merchants

edit: I can add clarification in the spec that min/maxSendable should NOT be removed in millisats so that the base spec still functions as expected

@ethanrose
Copy link
Contributor Author

ethanrose commented Mar 16, 2023

@hsjoberg hsjoberg self-requested a review March 16, 2023 13:39
@callebtc
Copy link
Contributor

callebtc commented Mar 16, 2023

This is far better than my solution that we've been running for a while: https://ln.tips/.well-known/lnurlp/callebtc?amount=$1 – the SERVER then converts the fiat amount to satoshis in the background and returns an invoice. The reason I resorted to this was that I wanted stupid WALLETs without knowing the conversion rate to be able to request a fiat amount.

If I understand correctly, what your PR does is to add the conversion rate to the first LNURLp response, I assume this is for UX reasons in order to be able to display to the user what the estimated amount in satoshi will be if they choose a fiat currency? Why would you not use the conversion rate returned by the payee to convert the fiat value to sats and then ask for a normal sat-denominated invoice?

So as far as I can see, your approach does it both ways: payee responds with a conversion rate (first response) and payer needs to request an invoice denominated in fiat (second request). My (bad) approach does only the second part. But I think it could be solved by only using the first part. It seems to me this would be better (less moving parts):

  • WALLET requests first response
  • SERVER responds with what you have suggested (which fiat currencies and what conversion rate they accept)
  • WALLET uses the fiat rate to calculate the equivalent amount in sats for a fiat price
  • WALLET requests the invoice like with ordinary LNURL-pay (without the currency=xy part in your proposal).

That way, the LNURL server does not have to convert the fiat amount back to satoshis for the invoice in the second response. A drawback of this could be that the SERVER does not know that the payment was intended to be fiat-denominated which might affect how the incoming payment is displayed to the payee. Thoughts?

@ethanrose
Copy link
Contributor Author

ethanrose commented Mar 16, 2023

Your solution is conceptually very similar so I think we’re on the right (most intuitive) track!

As you guessed, the reason for the estimated rates is purely for UX so that the WALLET can do frontend validation — to preemptively warn the payer if they won’t have enough funds in their own currency to pay the resulting invoice. The wallet could fetch exchange rates from anywhere but it’s most convenient and accurate for the WALLET if the SERVICE returns their own rates.

The reason that our SERVICE would prefer any WALLET to specify the currency in the second step is because the exchange rate will have fluctuated within the 20 seconds from first callback to settlement — our merchants don’t want to be subjected to even 20 seconds of volatility — but our SERVICE absorbs that volatility (for those 120 seconds that we are locking in the rate until the invoice expires).

You make a great point that most SERVICEs don’t care about locking rates. Would it be better for your SERVICE if we included a ‘lockingPreferred: true’ flag in the payrequest details which tells the WALLET to send the callback amount in fiat (to lock the rate) vs millisats (less complexity for the service)?

@ethanrose
Copy link
Contributor Author

TL;DR: Most services don't benefit from the second step, but do benefit from the first step.

Looking back, my proposed "flag" isn't named well at all. How about: currency: { supportedInCallback: true } so that the service can inform the wallet whether it wants the callback to be made in millisats vs in currency?

@callebtc @fiatjaf @hsjoberg Any concerns with adding a flag for it? Any other concerns? Our Bitcoin Island Conference is next weekend -- it would be powerful if we could announce the acceptance of LUD-21, because even though the central bank are not (yet) bitcoiners, they DO desire the Philippines to be a fintech leader.

@trbouma
Copy link

trbouma commented Mar 18, 2023

I support this PR. I tried your live example and it did not break my wallets. I will start experiencing/implementing in my service https://asats.io

@ethanrose
Copy link
Contributor Author

ethanrose commented Mar 20, 2023

@callebtc I updated the draft spec:

## 3. Including `currency` in callback parameters

Some services need to lock-in an exact exchange rate in local currency, while other services prefer using the unmodified LNURL-Pay flow.

If SERVICE includes `currency.currencyParamSupported: true`, wallet must add a `currency` query parameter to the callback, and specify the `amount` in the currency:

diff
- <callback><?|&>amount=<milliSatoshi>
+ <callback><?|&>amount=<philippinePeso>&currency=PHP

However, if `currency.currencyParamSupported` is false or undefined, the wallet must use millisats (standard LNURL-Pay callback!)

@livingroomofsatoshi
Copy link

livingroomofsatoshi commented Mar 20, 2023

Let's discuss at LightningCon :)

@fiatjaf
Copy link
Collaborator

fiatjaf commented Mar 20, 2023

Some important comments on Telegram that I think must be addressed: https://t.me/lnurl/35117

Basically: isn't the exchange rate going to vary anyway in the meantime?
If the flow is:

  1. service gives wallet an exchange rate
  2. wallet uses that exchange rate to calculate the amount
  3. 10min pass
  4. wallet requests invoice with the previous exchange rate

Isn't this the same as the service precalculating the exchange rate and just sending the amount in satoshis?, i.e. is this just intended for wallets to display the fiat currency, but doesn't really add exchange rate guarantees?

Or, if this is not the intended flow, but instead the service will calculate the exchange rate at the time of the second callback when it actually issues the lightning invoice, then how is the wallet going to be sure it is not being robbed? There are ways, of course, but then flow is not backwards-compatible (which could be ok too).

@ethanrose
Copy link
Contributor Author

ethanrose commented Mar 21, 2023

Per feedback, I've just simplified/clarified the proposal!

@vindard
Copy link

vindard commented Mar 21, 2023

Following, this is quite interesting for us at Galoy to be able to request USD-denominated invoices directly for a user's lightning address.

@ethanrose
Copy link
Contributor Author

ethanrose commented Mar 21, 2023

@vindard awesome! Yes I noticed that El Zonte / El Savlador has the same issues as the Philippines (we've all been printing out QR codes that direct to a web browser, but it's often a not very intuitive and confusing experience). i.e. If I recall correctly the vendors at Hope House are printing out their Bitcoin Beach Wallet "web-based payment gateway" QR codes. That is how we've started in Bitcoin Island Philippines as well

@vindard
Copy link

vindard commented Mar 21, 2023

If I recall correctly the vendors at Hope House are printing out their Bitcoin Beach Wallet "web-based payment gateway" QR codes. That is how we've started in Bitcoin Island Philippines as well

That's how it used to be, but now there's an lnurl-pay in there as well that some wallets can use to bypass the web browser.

We have a concept of a "default wallet" for each user as well that can be either sats denominated or USD (stablesats) denominated that the lightning address ties back to, and it's currently not easy to support if the default wallet is a USD (stablesats) one from the lightning address

@ethanrose
Copy link
Contributor Author

ethanrose commented Mar 22, 2023

Main outstanding concern from the telegram group chat:

  • A service can trick senders into accepting a bad exchange rate, if the sender quickly "accepts" the rate (that is, if sender chooses to trust instead of verify on the confirmation screen).

Proposed solution:

  • (Medium Effort) A wallet may wish to audit the exchange rate against market rate based on a 3rd party API, and then warn the user accordingly
  • (Low Effort) A wallet may offer a generic warning along the lines of Caution: This exchange rate may differ from market rate!

I believe that these consumer protection features should be strongly recommended but not absolutely required.

I'll wait a day for feedback & proceed to update the proposal!

@ethanrose
Copy link
Contributor Author

ethanrose commented Mar 23, 2023

Just took a video demo of the problem: https://t.me/lnurl/35187

…thod for dishonest services to trick senders.
@trbouma
Copy link

trbouma commented Mar 30, 2023

I've done an initial implementation so that it can be supported with asats.io Lightning addresses. I've implemented a configuration option where the wallet holder can set their local currency, and the service does the conversion to sats when the local currency amount gets passed back. Tested it with the pouch.ph wallet and it works fine according to the spec. So far I am only supporting PHP (Philippine Peso) and do a hardcode hack for the conversion (66 sats/peso). When I have a chance i will plug in a rate lookup service.

This is a great proposal, because the receiver can now control the conversion rate (not rely on the user) and for normies, everything can be presented in the local currency but all settled in sats in the background.

@trbouma
Copy link

trbouma commented Mar 30, 2023

I am wondering if the better title might be "Specify a payment amount in local unit of account" For example I might wants to send $20CAD to someone in the Philippines to buy a couple of beers for someone, but have no clue, or care what the local currency or exchange rate is. On the receiving side, the recipient sees 800PHP (the 20 bucks) show up in their wallet. The exchange rate and settling in sats is all taken care in the background, and the users just see it in the local unit of account.

The reason I am using the term "local unit of account" is because it distances away from "local currency". As a Canadian, my brain is wired to see everything priced in CAD, but that does not mean I have to transact in CAD dollars. I know that 20 bucks CAD should buy a couple of beers and an order of small fries. I am assuming it would be roughly the same in the Phillipines (or not!) without trying to figure out the local currency.

@ethanrose ethanrose changed the title LUD-21: Specify a payment amount in recipient's own currency LUD-21: Specify a payment amount in local unit of account Mar 31, 2023
@ethanrose
Copy link
Contributor Author

ethanrose commented Mar 31, 2023

@trbouma Great suggestion -- I've just updated the title(s) to "local unit of account".

Machankura (https://8333.mobi/) is implementing the spec already too, and they requested to add back the rateEstimate so that's in the above commit ^ just as an optional resource but not required for the wallet to do anything with!

@trbouma
Copy link

trbouma commented Mar 31, 2023

Nice. I just saw on Telegram that @fiatjaf is proposing to use multiplier versus exchange rate. I like that terminology because local unit of account multiplier can be distinguished from local currency exchange rate where the latter implies an amount that actually needs to be converted into the local currency.

I have been experimenting with Macankura as well. When I get back in later April, my first order of business will be to implement the exchange rate lookup in my service.

It will be cool when we get this all implemented!

@ethanrose
Copy link
Contributor Author

Updated rateEstimate to multiplier instead.
Before: rateEstimate: 1503781 (1 BTC to n PHP)
Now: multiplier: 66499 (1 PHP to n millisats)

Is that what you had in mind @trbouma? We've also made the change in our production service so folks can test it out! (https://app.pouch.ph/.well-known/lnurlp/ethan)

@trbouma
Copy link

trbouma commented Apr 1, 2023

I implemented that change and tested. It works! I need to do some work on my sender app to display in local currency and do the lookups for the multiplier. I am away for three weeks, so I'll tackle this later in April.

@trbouma
Copy link

trbouma commented Apr 1, 2023

I implemented that change and tested. It works! I need to do some work on my sender app to display in local currency and do the lookups for the multiplier. I am away for three weeks, so I'll tackle this later in April.

I got my sender app working to specify in local currency (PHP) for now. I can now send and receive pesos without thinking about exchange rates and sats.

@trbouma
Copy link

trbouma commented Apr 2, 2023

I prefer your approach @pouchceo vs what @fiatjaf is propose. . I don’t want to add mandatory fiat parameters that the wallet has to calculate. It might be a very simple service making the call in fiat amount only, expecting the service to make the conversion. The wallet could provide an optional multiplier_floor that the service can’t go below, and abort the transaction instead.

@trbouma
Copy link

trbouma commented May 3, 2023

Hi @ethanrose - I am continuing to implement LUD-21. It is working and you should have received a couple of payments in your wallet. I had to change some variables to float so I could accommodate the local current, such as 1.25 PHP, etc. I also have implemented a currency rate lookup from https://blockchain.info/ticker and shimmed in a couple of unsupported currencies such as PHP. I have also been testing from pouch.ph back to my asats test account which is configured for PHP - [email protected]. All in all, this is looking super-good!

@trbouma
Copy link

trbouma commented May 5, 2023

I have it all working and testing with the asats.io service I am building. See some tweets here: https://twitter.com/anonsats/status/1654097868477562880?s=20.

I can pretty much send in any currency denomination, I want. I am using the blockchain.info ticker and I have shimmed in some of other currencies - VND, PHP and I got a request from Africa to add in the naira (NGN) and CAF (XOT).

As for supporting payment apps, I can set a asats.io account to a local currency. For example [email protected] is set to CAD, and if the payment app supports it, it can pickup the conversion rate and present everything in CAD. This is the case with @ethanrose Pouch wallet, which supports PHP, CAD and USD. When I pay to the [email protected], it is all negotiated in CAD!

It is important to understand, that the service is calculating the exchange rate - not the end user wallet. So you no longer have to trust each wallet app is doing the right conversion, the service takes care of conversion. Also, the service is motivated to provide the most accurate and fair conversion rates, because there is no actual currency buy/sell exchange taking place - everything is still in sats. There is absolutely no motivation for the `service' to game the rates because there is no way to collect on the transaction, aside from the usual routing fees from the underlying node, or a fee when the user clears out their account to another provider.

Anyway, this works. While wallet apps can do the conversion in their app, doing it at the service level means less points of trust. Yeah, some might not want to trust the service provider (asats.io) - same goes for wallet apps - users can vote with their feet, if they don't like it.

@trbouma
Copy link

trbouma commented Aug 9, 2023

Any progress on this lud being accepted? I have implemented and having it working with over 50 local currencies. @ethanrose

@trbouma
Copy link

trbouma commented Sep 30, 2023

Hi - wondering if there has been any progress on reviewing/accepting this pull request?

@ethanrose @fiatjaf

@jklein24
Copy link

I would also love to offer our support for this proposal! We have it implemented internally with one minor change - some of our customers may support receiving in multiple different currencies. If a user is willing to receive either USD or BTC, it would be nice for the currency field to instead be a plural currencies array, sorted in order of preference. The structure of each object in that array would be as specified here. @ethanrose did you consider supporting multiple currencies like this? I think it adds a bit more flexibility because the sender can select one that they support.

@jklein24
Copy link

FYI, this proposal is the basis for the FX mechanism in UMA. See the protocol here including a link to this PR. All companies onboarding to UMA (Ripio, Bitnob, zerohash, bakkt, etc) have implemented this LUD with the caveat I mentioned last week (to allow multiple currencies).

@jklein24
Copy link

One other suggested change here that I already relayed to @ethanrose after a request from one of our customers -

As the sending wallet, you aren't sure how to display the target currency because you don't really know what the "smallest unit" of the target currency is. To solve this, in UMA, we added a displayDecimals field in the currency description object. For example, displayDecimals would be set to 2 for USD because usually you display cents with 2 decimals. Change in UMA: https://github.com/uma-universal-money-address/protocol/pull/7/files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants