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

[feature]: generate asset invoice with fix amount of SATs instead of asset amount #1440

Open
saraogiraj94 opened this issue Mar 13, 2025 · 19 comments · May be fixed by #1448
Open

[feature]: generate asset invoice with fix amount of SATs instead of asset amount #1440

saraogiraj94 opened this issue Mar 13, 2025 · 19 comments · May be fixed by #1448
Labels
enhancement New feature or request needs triage

Comments

@saraogiraj94
Copy link

Is your feature request related to a problem? Please describe.
We are working on receiving assets on LNURL /LN Address, currently, when a user requests an LN invoice for a fixed amount in SATs (e.g., 1100 SATs) for given LNURL , since we know that this LNURL is linked to asset we convert this amount into an equivalent USDT-L (Taproot Asset) based on our exchange rate and generate a USDT-L invoice in the background. However, due to slight fluctuations in the exchange rate or rounding mechanisms at price oracle, the SATs value of the generated invoice may slightly differ (e.g., 1105 SATs instead of 1100).

This discrepancy causes an issue where the payer's wallet (expecting an invoice of exactly 1100 SATs) rejects the payment since the decoded invoice shows a different SATs amount than initially requested.

Describe the solution you'd like
We propose enhancing the "generate invoice" API in Tapd to allow specifying the invoice amount in SATs rather then asset amount while still issuing an asset-backed (USDT-L) invoice. This means:

The requested SATs amount remains fixed (e.g., 1100 SATs).
The backend handles the conversion and ensures the asset invoice (USDT-L) internally matches the requested SATs amount.
The payer sees an invoice with the expected SATs amount, preventing wallet rejection.
This would allow seamless integration of USDT-L payments within LNURL without breaking existing user expectations.

Describe alternatives you've considered
Wallet-side adaptation: Requesting LN wallets to support a small margin of deviation in invoice amounts, but this is not widely supported and would require broad ecosystem changes.

Additional context
This issue primarily affects Lightning Address (LNURL) payments where users expect fixed SATs amounts. If TAPD allows asset-backed invoices with a fixed SATs value, it would enhance stablecoin interoperability in Lightning without breaking user expectations.

Below is the current journey of payment

  1. Alice wants to send 1100 SATs on LN Address of Bob
  2. Alice Pings our wallet for an LN Invoice of 1100 SATs
  3. At wallet we know that this LN Address created by Bob is meant for USDT-L so he is expecting his asset (USDT-L) balance to increase.
  4. We convert the 1100 SATs to say 1.1 USDT-L based on exchange rate and generate invoice of it, and due to price oracle at node the SATs value of invoice become 1105 SATs.
  5. When Alice receives invoice and decode it it sees that amount in invoice is 1105 SATs
  6. Now since the amount is different (1105) then what was requested (1100) the wallet of Alice denies the operation.
@saraogiraj94 saraogiraj94 added the enhancement New feature or request label Mar 13, 2025
@ZZiigguurraatt
Copy link

4. We convert the 1100 SATs to say 1.1 USDT-L based on exchange rate and generate invoice of it, and due to price oracle at node the SATs value of invoice become 1105 SATs.

Can you give more details to why this happens on your end? Wondering if this problem could be resolved within the price oracle instead of tapd.

@saraogiraj94
Copy link
Author

So what we do is that we get 1100 SATs, which we convert at how many assets should we make invoice of, and based on that amount we call tapd generate invoice API
Say with conversion of 1100 SATs we get 1 USDT-L asset, and in generate asset invoice we call with asset amount as 1.

Now node uses its own price oracle based on edge node, so at node level based on price the value of 1 USDT-L (Asset) is 1105 SATs, and thus node returns us an invoice of 1105 SATs.
@ZZiigguurraatt hope this makes you clear ?

@ZZiigguurraatt
Copy link

Confused why the price oracle used by tapd uses a slightly different exchange rate from the application that fetches the invoice from the tapd node? Is it a timing issue or they have a different exchange rate source?

@saraogiraj94
Copy link
Author

Price oracle depends on edge node, so its exchange rate may be different, also there is time delay which can cause slight variation, so both may be possible.

@ZZiigguurraatt
Copy link

It seems to me like the proper way to do it would be to use AddAssetBuyOrder to lock in an RFQ with the peer, then create an invoice request using the exchange rate locked in with the rfq_id. Currently we don't have a way to use AddInvoice with a user supplied rfq id, but I think that might be a useful feature to add (especially since SendPayment already does allow passing a rfq_id from AddAssetSellOrder).

@ZZiigguurraatt
Copy link

Currently we don't have a way to use AddInvoice with a user supplied rfq id, but I think that might be a useful feature to add (especially since SendPayment already does allow passing a rfq_id from AddAssetSellOrder).

Created an issue for that: #1442 .

Let us know if you are okay closing this issue in favor of #1442 .

@saraogiraj94
Copy link
Author

@ZZiigguurraatt IMO both are bit different, if I am the user creating invoice then can make use of rfg_id, but in our case third party user is requesting invoice of given SATs, so SATs amount is fixed, he/she is not at all aware of any rfq_id or price oracle.

Just to let you know, rfq_id while AddAssetBuyOrder is also good idea so that at application level only we can show the exchange price and invoice gets created with the same rate.
But my case is totally different as SATs amount is fixed.

@ZZiigguurraatt
Copy link

Consider this scenario:

  1. Third party requests to pay 1,100 sat
  2. You do an RFQ with your peer using AddAssetBuyOrder and agree to 1,000 SATs to say 1.0 USDT-L
  3. You calculate that you should request an invoice for 1.1 USDT-L and make that request and reference the rfq_id previously obtained (if [feature]: AddInvoice: allow specifying an rfq_id #1442 were fixed).
  4. Invoice is returned for 1,100 SAT
  5. Invoice is returned to the third party and they pay it because it is what they expected to see.

How does this not work?

@saraogiraj94
Copy link
Author

@ZZiigguurraatt it works theoretically but I hope when we get exchange rate of 1000 SATs = 1.0 USDT-L and calculate say 1.1 USDT-L invoice to be made at node also it will be exactly 1100 SATs no rounding of will be done at node level. is this possible to get exact or at node level few milisats may differ due to rounding issue.

If we are getting actual SATs then it should work, but I still doubt any rounding can change few milisats.

@jayneel007
Copy link

jayneel007 commented Mar 18, 2025

@ZZiigguurraatt The challenge with above approach is that getting the exact value in reverse calculation is difficult due to conversion rate fluctuations and rounding differences.

For example, let’s say the third party requests to pay 1,100 sats.
• You do an RFQ and agree on 1,000 sats = 1.0 USDT-L.
• You calculate and request an invoice for 1.1 USDT-L, expecting it to be 1,100 sats.
• However, when the invoice is generated, due to small conversion differences (e.g., network fees, rounding, or rate changes), it might return 1,102 sats instead of 1,100.
• If you pass this invoice to the third party, they might reject it since it doesn’t match their expected 1,100 sats.

Even a difference of a few sats can break the process because the third party expects an exact amount. This is why handling reverse calculations dynamically is tricky, and a more precise mechanism is needed to ensure exact payments.

Can you confirm if our understanding of this process is correct? If not, could you please explain in detail how we can achieve this accurately while ensuring the expected invoice amount matches exactly?

@ZZiigguurraatt
Copy link

@ZZiigguurraatt The challenge with above approach is that getting the exact value in reverse calculation is difficult due to conversion rate fluctuations and rounding differences.

Once the RFQ is negotiated and agreed upon, the rate doesn't change unless a new RFQ is negotiated and used. If #1442 is implemented, then we can ensure a new RFQ is not negotiated. We plan to fix #1442 first and if further demand we can consider fixing this issue too, as your original request here will definitely be simpler for application developers to use.

For example, let’s say the third party requests to pay 1,100 sats. • You do an RFQ and agree on 1,000 sats = 1.0 USDT-L. • You calculate and request an invoice for 1.1 USDT-L, expecting it to be 1,100 sats. • However, when the invoice is generated, due to small conversion differences (e.g., network fees, rounding, or rate changes), it might return 1,102 sats instead of 1,100. • If you pass this invoice to the third party, they might reject it since it doesn’t match their expected 1,100 sats.

As mentioned above, fixing #1442 should allow the exchange rate to be locked.

There should not be any network fees added for the last hop as the edge node's buy/sell price spread should be how they profit from the asset conversion.

After fixing #1442, I think we should check to see if we actually have any roundoff error. We have some discussion about rounding in #1391 . It's possible due to roundoff error, fixing #1442 will not be good enough and we do have to fix it the way you are requesting here, but I think we need to fix #1442 first.

Even a difference of a few sats can break the process because the third party expects an exact amount. This is why handling reverse calculations dynamically is tricky, and a more precise mechanism is needed to ensure exact payments.

Can you confirm if our understanding of this process is correct? If not, could you please explain in detail how we can achieve this accurately while ensuring the expected invoice amount matches exactly?

@ZZiigguurraatt
Copy link

After fixing #1442, I think we should check to see if we actually have any roundoff error. We have some discussion about rounding in #1391 . It's possible due to roundoff error, fixing #1442 will not be good enough and we do have to fix it the way you are requesting here, but I think we need to fix #1442 first.

I've been thinking about this more and likely we could have a 2 sat roundoff error. Also, I think you can do the following to get what you want:

  1. Third party requests to pay 1,100 sat
  2. You do an RFQ with your peer using AddAssetBuyOrder and agree to 1,000 SATs to say 1.0 USDT-L (probably make sure you set asset_max_amt a bit higher than 1.1 USDT-L when making the request).
  3. Take the response from AddAssetBuyOrder and in the accepted_quote, fetch peer and scid.
  4. Use LND's AddInvoice and generate an invoice for 1,100 SAT and add to route_hint with a hop_hint with the peer as node_id and scid as chan_id. Adding the hop hint will tie this invoice to the RFQ.

You can see some inspiration for this process here: https://github.com/lightninglabs/lightning-terminal/blob/f082579252e4b6d1f9dd84e74646ec3e9b7e8e33/itest/litd_custom_channels_test.go#L2122-L2156 .

We are still considering allowing TAPD's AddInvoice to allow the amount to be specified in sats instead (so you don't have to do the manual RFQ and hop hint generation as described above), but I think the above process (although a few extra steps) should achieve the same outcome.

@ZZiigguurraatt
Copy link

ZZiigguurraatt commented Mar 19, 2025

Also, we may want to create another new issue to change AddAssetBuyOrder to accept sat_max_amt in addition to asset_max_amt so that the above workflow you don't need to guess that asset_max_amt should need to be set a bit higher than 1.1 USDT-L

@saraogiraj94
Copy link
Author

@ZZiigguurraatt what if to keep developer flow simple
https://lightning.engineering/api-docs/api/taproot-assets/taproot-asset-channels/add-invoice/
in this add new key name, amount_msats : Meaning the invoice of MSATs you want and keep it mutually exclusive with asset_amount and handle all the buy order logic behind the scenes. As a third party developer with this change we can avail multiple functionality

  1. Create invoice of X Assets, tending to Y SATs using asset_amount key
  2. Create invoice of X SATs, tending to Y Assets, via amount_msats key

@ZZiigguurraatt
Copy link

@ZZiigguurraatt what if to keep developer flow simple https://lightning.engineering/api-docs/api/taproot-assets/taproot-asset-channels/add-invoice/ in this add new key name, amount_msats : Meaning the invoice of MSATs you want and keep it mutually exclusive with asset_amount and handle all the buy order logic behind the scenes. As a third party developer with this change we can avail multiple functionality

1. Create invoice of X Assets, tending to Y SATs using asset_amount key

2. Create invoice of X SATs, tending to Y Assets, via amount_msats key

Yes, I think this is the goal (possibly also adding amount_sat as well as an option) but you can try to use the workflow I've defined in #1440 (comment) for now until we have exactly what you are asking for.

@saraogiraj94
Copy link
Author

@ZZiigguurraatt sure let us try with the flow provided, thanks !

@GeorgeTsagk GeorgeTsagk linked a pull request Mar 21, 2025 that will close this issue
@Roasbeef
Copy link
Member

We've implemented this here as an extension to AddInvoice: #1448

@ZZiigguurraatt
Copy link

We've implemented this here as an extension to AddInvoice: #1448

Isn't this just a draft still and not fully implemented? I know that https://github.com/lightninglabs/taproot-assets/blob/main/taprpc/tapchannelrpc/tapchannel.proto and https://github.com/lightninglabs/taproot-assets/blob/main/taprpc/tapchannelrpc/tapchannel.swagger.json need to be updated at least before it's actually usable, right?

@ZZiigguurraatt
Copy link

Also, we may want to create another new issue to change AddAssetBuyOrder to accept sat_max_amt in addition to asset_max_amt so that the above workflow you don't need to guess that asset_max_amt should need to be set a bit higher than 1.1 USDT-L

Created another issue for that: #1459 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request needs triage
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants