@@ -19,13 +19,17 @@ use std::vec::IntoIter;
1919
2020use bip21:: de:: ParamKind ;
2121use bip21:: { DeserializationError , DeserializeParams , Param , SerializeParams } ;
22- use bitcoin:: address:: { NetworkChecked , NetworkUnchecked } ;
22+ use bitcoin:: address:: NetworkChecked ;
2323use bitcoin:: { Amount , Txid } ;
2424use lightning:: ln:: channelmanager:: PaymentId ;
2525use lightning:: offers:: offer:: Offer ;
2626use lightning:: routing:: router:: RouteParametersConfig ;
2727use lightning_invoice:: { Bolt11Invoice , Bolt11InvoiceDescription , Description } ;
2828
29+ use bitcoin_payment_instructions:: {
30+ amount:: Amount as BPIAmount , PaymentInstructions , PaymentMethod ,
31+ } ;
32+
2933use crate :: error:: Error ;
3034use crate :: ffi:: maybe_wrap;
3135use crate :: logger:: { log_error, LdkLogger , Logger } ;
@@ -138,63 +142,124 @@ impl UnifiedPayment {
138142 Ok ( format_uri ( uri) )
139143 }
140144
141- /// Sends a payment given a [BIP 21] URI.
145+ /// Sends a payment given a [BIP 21] URI or [BIP 353] HRN .
142146 ///
143147 /// This method parses the provided URI string and attempts to send the payment. If the URI
144148 /// has an offer and or invoice, it will try to pay the offer first followed by the invoice.
145149 /// If they both fail, the on-chain payment will be paid.
146150 ///
147- /// Returns a `QrPaymentResult ` indicating the outcome of the payment. If an error
151+ /// Returns a `UnifiedPaymentResult ` indicating the outcome of the payment. If an error
148152 /// occurs, an `Error` is returned detailing the issue encountered.
149153 ///
150154 /// If `route_parameters` are provided they will override the default as well as the
151155 /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
152156 ///
153157 /// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
154- pub fn send (
155- & self , uri_str : & str , route_parameters : Option < RouteParametersConfig > ,
158+ /// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
159+ pub async fn send (
160+ & self , uri_str : & str , amount_msat : Option < u64 > ,
161+ route_parameters : Option < RouteParametersConfig > ,
156162 ) -> Result < UnifiedPaymentResult , Error > {
157- let uri: bip21:: Uri < NetworkUnchecked , Extras > =
158- uri_str. parse ( ) . map_err ( |_| Error :: InvalidUri ) ?;
159-
160- let _resolver = & self . hrn_resolver ;
161-
162- let uri_network_checked =
163- uri. clone ( ) . require_network ( self . config . network ) . map_err ( |_| Error :: InvalidNetwork ) ?;
164-
165- if let Some ( offer) = uri_network_checked. extras . bolt12_offer {
166- let offer = maybe_wrap ( offer) ;
167-
168- match self . bolt12_payment . send ( & offer, None , None , route_parameters) {
169- Ok ( payment_id) => return Ok ( UnifiedPaymentResult :: Bolt12 { payment_id } ) ,
170- Err ( e) => log_error ! ( self . logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified QR code payment. Falling back to the BOLT11 invoice." , e) ,
171- }
172- }
173-
174- if let Some ( invoice) = uri_network_checked. extras . bolt11_invoice {
175- let invoice = maybe_wrap ( invoice) ;
176-
177- match self . bolt11_invoice . send ( & invoice, route_parameters) {
178- Ok ( payment_id) => return Ok ( UnifiedPaymentResult :: Bolt11 { payment_id } ) ,
179- Err ( e) => log_error ! ( self . logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified QR code payment. Falling back to the on-chain transaction." , e) ,
180- }
181- }
182-
183- let amount = match uri_network_checked. amount {
184- Some ( amount) => amount,
185- None => {
186- log_error ! ( self . logger, "No amount specified in the URI. Aborting the payment." ) ;
187- return Err ( Error :: InvalidAmount ) ;
163+ let instructions = PaymentInstructions :: parse (
164+ uri_str,
165+ self . config . network ,
166+ self . hrn_resolver . as_ref ( ) ,
167+ false ,
168+ )
169+ . await
170+ . map_err ( |e| {
171+ log_error ! ( self . logger, "Failed to parse payment instructions: {:?}" , e) ;
172+ Error :: UriParameterParsingFailed
173+ } ) ?;
174+
175+ let resolved = match instructions {
176+ PaymentInstructions :: ConfigurableAmount ( instr) => {
177+ let amount = amount_msat. ok_or_else ( || {
178+ log_error ! ( self . logger, "No amount specified. Aborting the payment." ) ;
179+ Error :: InvalidAmount
180+ } ) ?;
181+
182+ let amt = BPIAmount :: from_milli_sats ( amount) . map_err ( |e| {
183+ log_error ! ( self . logger, "Error while converting amount : {:?}" , e) ;
184+ Error :: InvalidAmount
185+ } ) ?;
186+
187+ instr. set_amount ( amt, self . hrn_resolver . as_ref ( ) ) . await . map_err ( |e| {
188+ log_error ! ( self . logger, "Failed to set amount: {:?}" , e) ;
189+ Error :: InvalidAmount
190+ } ) ?
191+ } ,
192+ PaymentInstructions :: FixedAmount ( instr) => {
193+ if let Some ( user_amount) = amount_msat {
194+ if instr. max_amount ( ) . map_or ( false , |amt| user_amount < amt. milli_sats ( ) ) {
195+ log_error ! ( self . logger, "Amount specified is less than the amount in the parsed URI. Aborting the payment." ) ;
196+ return Err ( Error :: InvalidAmount ) ;
197+ }
198+ }
199+ instr
188200 } ,
189201 } ;
190202
191- let txid = self . onchain_payment . send_to_address (
192- & uri_network_checked. address ,
193- amount. to_sat ( ) ,
194- None ,
195- ) ?;
203+ let mut sorted_payment_methods = resolved. methods ( ) . to_vec ( ) ;
204+ sorted_payment_methods. sort_by_key ( |method| match method {
205+ PaymentMethod :: LightningBolt12 ( _) => 0 ,
206+ PaymentMethod :: LightningBolt11 ( _) => 1 ,
207+ PaymentMethod :: OnChain ( _) => 2 ,
208+ } ) ;
209+
210+ for method in sorted_payment_methods {
211+ match method {
212+ PaymentMethod :: LightningBolt12 ( offer) => {
213+ let offer = maybe_wrap ( offer. clone ( ) ) ;
214+
215+ let payment_result = if let Some ( amount_msat) = amount_msat {
216+ self . bolt12_payment . send_using_amount ( & offer, amount_msat, None , None , route_parameters)
217+ } else {
218+ self . bolt12_payment . send ( & offer, None , None , route_parameters)
219+ }
220+ . map_err ( |e| {
221+ log_error ! ( self . logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified payment. Falling back to the BOLT11 invoice." , e) ;
222+ e
223+ } ) ;
224+
225+ if let Ok ( payment_id) = payment_result {
226+ return Ok ( UnifiedPaymentResult :: Bolt12 { payment_id } ) ;
227+ }
228+ } ,
229+ PaymentMethod :: LightningBolt11 ( invoice) => {
230+ let invoice = maybe_wrap ( invoice. clone ( ) ) ;
231+ let payment_result = self . bolt11_invoice . send ( & invoice, route_parameters)
232+ . map_err ( |e| {
233+ log_error ! ( self . logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified payment. Falling back to the on-chain transaction." , e) ;
234+ e
235+ } ) ;
236+
237+ if let Ok ( payment_id) = payment_result {
238+ return Ok ( UnifiedPaymentResult :: Bolt11 { payment_id } ) ;
239+ }
240+ } ,
241+ PaymentMethod :: OnChain ( address) => {
242+ let amount = resolved. onchain_payment_amount ( ) . ok_or_else ( || {
243+ log_error ! ( self . logger, "No amount specified. Aborting the payment." ) ;
244+ Error :: InvalidAmount
245+ } ) ?;
246+
247+ let amt_sats = amount. sats ( ) . map_err ( |_| {
248+ log_error ! (
249+ self . logger,
250+ "Amount in sats returned an error. Aborting the payment."
251+ ) ;
252+ Error :: InvalidAmount
253+ } ) ?;
254+
255+ let txid = self . onchain_payment . send_to_address ( & address, amt_sats, None ) ?;
256+ return Ok ( UnifiedPaymentResult :: Onchain { txid } ) ;
257+ } ,
258+ }
259+ }
196260
197- Ok ( UnifiedPaymentResult :: Onchain { txid } )
261+ log_error ! ( self . logger, "Payable methods not found in URI" ) ;
262+ Err ( Error :: PaymentSendingFailed )
198263 }
199264}
200265
@@ -321,7 +386,8 @@ impl DeserializationError for Extras {
321386
322387#[ cfg( test) ]
323388mod tests {
324- use super :: { Amount , Bolt11Invoice , Extras , Offer } ;
389+ use super :: * ;
390+ use crate :: payment:: unified:: Extras ;
325391 use bitcoin:: { address:: NetworkUnchecked , Address , Network } ;
326392 use std:: str:: FromStr ;
327393
0 commit comments