[feature]: generate asset invoice with fix amount of SATs instead of asset amount
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
- Alice wants to send 1100 SATs on LN Address of Bob
- Alice Pings our wallet for an LN Invoice of 1100 SATs
- 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.
- 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.
- When Alice receives invoice and decode it it sees that amount in invoice is 1105 SATs
- Now since the amount is different (1105) then what was requested (1100) the wallet of Alice denies the operation.
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.
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 ?
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?
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.
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).
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 arfq_idfrom AddAssetSellOrder).
Created an issue for that: https://github.com/lightninglabs/taproot-assets/issues/1442 .
Let us know if you are okay closing this issue in favor of https://github.com/lightninglabs/taproot-assets/issues/1442 .
@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.
Consider this scenario:
- Third party requests to pay 1,100 sat
- You do an RFQ with your peer using AddAssetBuyOrder and agree to 1,000 SATs to say 1.0 USDT-L
- You calculate that you should request an invoice for 1.1 USDT-L and make that request and reference the
rfq_idpreviously obtained (if https://github.com/lightninglabs/taproot-assets/issues/1442 were fixed). - Invoice is returned for 1,100 SAT
- Invoice is returned to the third party and they pay it because it is what they expected to see.
How does this not work?
@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.
@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 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 https://github.com/lightninglabs/taproot-assets/issues/1442 is implemented, then we can ensure a new RFQ is not negotiated. We plan to fix https://github.com/lightninglabs/taproot-assets/issues/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 https://github.com/lightninglabs/taproot-assets/issues/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 https://github.com/lightninglabs/taproot-assets/issues/1442, I think we should check to see if we actually have any roundoff error. We have some discussion about rounding in https://github.com/lightninglabs/taproot-assets/issues/1391 . It's possible due to roundoff error, fixing https://github.com/lightninglabs/taproot-assets/issues/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 https://github.com/lightninglabs/taproot-assets/issues/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?
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:
- Third party requests to pay 1,100 sat
- 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_amta bit higher than 1.1 USDT-L when making the request). - Take the response from AddAssetBuyOrder and in the
accepted_quote, fetchpeerandscid. - Use LND's AddInvoice and generate an invoice for 1,100 SAT and add to
route_hintwith ahop_hintwith thepeerasnode_idandscidaschan_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.
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
@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
- Create invoice of X Assets, tending to Y SATs using asset_amount key
- Create invoice of X SATs, tending to Y Assets, via amount_msats key
@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 withasset_amountand handle all the buy order logic behind the scenes. As a third party developer with this change we can avail multiple functionality1. 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 https://github.com/lightninglabs/taproot-assets/issues/1440#issuecomment-2735280510 for now until we have exactly what you are asking for.
@ZZiigguurraatt sure let us try with the flow provided, thanks !
We've implemented this here as an extension to AddInvoice: https://github.com/lightninglabs/taproot-assets/pull/1448
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?
Also, we may want to create another new issue to change AddAssetBuyOrder to accept
sat_max_amtin addition toasset_max_amtso that the above workflow you don't need to guess thatasset_max_amtshould need to be set a bit higher than 1.1 USDT-L
Created another issue for that: https://github.com/lightninglabs/taproot-assets/issues/1459 .