@@ -11,6 +11,7 @@ use bitcoin::secp256k1::PublicKey;
1111use lightning:: ln:: channelmanager:: { PaymentId , RecipientOnionFields , Retry } ;
1212use lightning:: ln:: msgs:: SocketAddress ;
1313use lightning:: ln:: { ChannelId , PaymentHash , PaymentPreimage } ;
14+ use lightning:: offers:: offer:: { self , Offer } ;
1415use lightning:: onion_message:: messenger:: Destination ;
1516use lightning:: onion_message:: packet:: OnionMessageContents ;
1617use lightning:: routing:: gossip:: NodeId ;
@@ -19,6 +20,8 @@ use lightning::sign::{EntropySource, KeysManager};
1920use lightning:: util:: config:: { ChannelHandshakeConfig , ChannelHandshakeLimits , UserConfig } ;
2021use lightning:: util:: persist:: KVStore ;
2122use lightning:: util:: ser:: { Writeable , Writer } ;
23+ use lightning_invoice:: payment:: payment_parameters_from_invoice;
24+ use lightning_invoice:: payment:: payment_parameters_from_zero_amount_invoice;
2225use lightning_invoice:: { utils, Bolt11Invoice , Currency } ;
2326use lightning_persister:: fs_store:: FilesystemStore ;
2427use std:: env;
@@ -72,7 +75,7 @@ pub(crate) fn poll_for_user_input(
7275 ) ;
7376 println ! ( "LDK logs are available at <your-supplied-ldk-data-dir-path>/.ldk/logs" ) ;
7477 println ! ( "Local Node ID is {}." , channel_manager. get_our_node_id( ) ) ;
75- loop {
78+ ' read_command : loop {
7679 print ! ( "> " ) ;
7780 io:: stdout ( ) . flush ( ) . unwrap ( ) ; // Without flushing, the `>` doesn't print
7881 let mut line = String :: new ( ) ;
@@ -160,20 +163,90 @@ pub(crate) fn poll_for_user_input(
160163 continue ;
161164 }
162165
163- let invoice = match Bolt11Invoice :: from_str ( invoice_str. unwrap ( ) ) {
164- Ok ( inv) => inv,
165- Err ( e) => {
166- println ! ( "ERROR: invalid invoice: {:?}" , e) ;
166+ let mut user_provided_amt: Option < u64 > = None ;
167+ if let Some ( amt_msat_str) = words. next ( ) {
168+ match amt_msat_str. parse ( ) {
169+ Ok ( amt) => user_provided_amt = Some ( amt) ,
170+ Err ( e) => {
171+ println ! ( "ERROR: couldn't parse amount_msat: {}" , e) ;
172+ continue ;
173+ }
174+ } ;
175+ }
176+
177+ if let Ok ( offer) = Offer :: from_str ( invoice_str. unwrap ( ) ) {
178+ let offer_hash = Sha256 :: hash ( invoice_str. unwrap ( ) . as_bytes ( ) ) ;
179+ let payment_id = PaymentId ( * offer_hash. as_ref ( ) ) ;
180+
181+ let amt_msat = match ( offer. amount ( ) , user_provided_amt) {
182+ ( Some ( offer:: Amount :: Bitcoin { amount_msats } ) , _) => * amount_msats,
183+ ( _, Some ( amt) ) => amt,
184+ ( amt, _) => {
185+ println ! ( "ERROR: Cannot process non-Bitcoin-denominated offer value {:?}" , amt) ;
186+ continue ;
187+ }
188+ } ;
189+ if user_provided_amt. is_some ( ) && user_provided_amt != Some ( amt_msat) {
190+ println ! ( "Amount didn't match offer of {}msat" , amt_msat) ;
167191 continue ;
168192 }
169- } ;
170193
171- send_payment (
172- & channel_manager,
173- & invoice,
174- & mut outbound_payments. lock ( ) . unwrap ( ) ,
175- Arc :: clone ( & fs_store) ,
176- ) ;
194+ while user_provided_amt. is_none ( ) {
195+ print ! ( "Paying offer for {} msat. Continue (Y/N)? >" , amt_msat) ;
196+ io:: stdout ( ) . flush ( ) . unwrap ( ) ;
197+
198+ if let Err ( e) = io:: stdin ( ) . read_line ( & mut line) {
199+ println ! ( "ERROR: {}" , e) ;
200+ break ' read_command;
201+ }
202+
203+ if line. len ( ) == 0 {
204+ // We hit EOF / Ctrl-D
205+ break ' read_command;
206+ }
207+
208+ if line. starts_with ( "Y" ) {
209+ break ;
210+ }
211+ if line. starts_with ( "N" ) {
212+ continue ' read_command;
213+ }
214+ }
215+
216+ outbound_payments. lock ( ) . unwrap ( ) . payments . insert (
217+ payment_id,
218+ PaymentInfo {
219+ preimage : None ,
220+ secret : None ,
221+ status : HTLCStatus :: Pending ,
222+ amt_msat : MillisatAmount ( Some ( amt_msat) ) ,
223+ } ,
224+ ) ;
225+ fs_store
226+ . write ( "" , "" , OUTBOUND_PAYMENTS_FNAME , & outbound_payments. encode ( ) )
227+ . unwrap ( ) ;
228+
229+ let retry = Retry :: Timeout ( Duration :: from_secs ( 10 ) ) ;
230+ let amt = Some ( amt_msat) ;
231+ let pay = channel_manager
232+ . pay_for_offer ( & offer, None , amt, None , payment_id, retry, None ) ;
233+ if pay. is_err ( ) {
234+ println ! ( "ERROR: Failed to pay: {:?}" , pay) ;
235+ }
236+ } else {
237+ match Bolt11Invoice :: from_str ( invoice_str. unwrap ( ) ) {
238+ Ok ( invoice) => send_payment (
239+ & channel_manager,
240+ & invoice,
241+ user_provided_amt,
242+ & mut outbound_payments. lock ( ) . unwrap ( ) ,
243+ Arc :: clone ( & fs_store) ,
244+ ) ,
245+ Err ( e) => {
246+ println ! ( "ERROR: invalid invoice: {:?}" , e) ;
247+ }
248+ }
249+ }
177250 }
178251 "keysend" => {
179252 let dest_pubkey = match words. next ( ) {
@@ -212,6 +285,34 @@ pub(crate) fn poll_for_user_input(
212285 Arc :: clone ( & fs_store) ,
213286 ) ;
214287 }
288+ "getoffer" => {
289+ let offer_builder = channel_manager. create_offer_builder ( String :: new ( ) ) ;
290+ if let Err ( e) = offer_builder {
291+ println ! ( "ERROR: Failed to initiate offer building: {:?}" , e) ;
292+ continue ;
293+ }
294+
295+ let amt_str = words. next ( ) ;
296+ let offer = if amt_str. is_some ( ) {
297+ let amt_msat: Result < u64 , _ > = amt_str. unwrap ( ) . parse ( ) ;
298+ if amt_msat. is_err ( ) {
299+ println ! ( "ERROR: getoffer provided payment amount was not a number" ) ;
300+ continue ;
301+ }
302+ offer_builder. unwrap ( ) . amount_msats ( amt_msat. unwrap ( ) ) . build ( )
303+ } else {
304+ offer_builder. unwrap ( ) . build ( )
305+ } ;
306+
307+ if offer. is_err ( ) {
308+ println ! ( "ERROR: Failed to build offer: {:?}" , offer. unwrap_err( ) ) ;
309+ } else {
310+ // Note that unlike BOLT11 invoice creation we don't bother to add a
311+ // pending inbound payment here, as offers can be reused and don't
312+ // correspond with individual payments.
313+ println ! ( "{}" , offer. unwrap( ) ) ;
314+ }
315+ }
215316 "getinvoice" => {
216317 let amt_str = words. next ( ) ;
217318 if amt_str. is_none ( ) {
@@ -480,11 +581,12 @@ fn help() {
480581 println ! ( " disconnectpeer <peer_pubkey>" ) ;
481582 println ! ( " listpeers" ) ;
482583 println ! ( "\n Payments:" ) ;
483- println ! ( " sendpayment <invoice> " ) ;
584+ println ! ( " sendpayment <invoice|offer> [<amount_msat>] " ) ;
484585 println ! ( " keysend <dest_pubkey> <amt_msats>" ) ;
485586 println ! ( " listpayments" ) ;
486587 println ! ( "\n Invoices:" ) ;
487588 println ! ( " getinvoice <amt_msats> <expiry_secs>" ) ;
589+ println ! ( " getoffer [<amt_msats>]" ) ;
488590 println ! ( "\n Other:" ) ;
489591 println ! ( " signmessage <message>" ) ;
490592 println ! (
@@ -686,12 +788,41 @@ fn open_channel(
686788}
687789
688790fn send_payment (
689- channel_manager : & ChannelManager , invoice : & Bolt11Invoice ,
791+ channel_manager : & ChannelManager , invoice : & Bolt11Invoice , required_amount_msat : Option < u64 > ,
690792 outbound_payments : & mut OutboundPaymentInfoStorage , fs_store : Arc < FilesystemStore > ,
691793) {
692794 let payment_id = PaymentId ( ( * invoice. payment_hash ( ) ) . to_byte_array ( ) ) ;
693- let payment_hash = PaymentHash ( ( * invoice. payment_hash ( ) ) . to_byte_array ( ) ) ;
694795 let payment_secret = Some ( * invoice. payment_secret ( ) ) ;
796+ let zero_amt_invoice =
797+ invoice. amount_milli_satoshis ( ) . is_none ( ) || invoice. amount_milli_satoshis ( ) == Some ( 0 ) ;
798+ let pay_params_opt = if zero_amt_invoice {
799+ if let Some ( amt_msat) = required_amount_msat {
800+ payment_parameters_from_zero_amount_invoice ( invoice, amt_msat)
801+ } else {
802+ println ! ( "Need an amount for the given 0-value invoice" ) ;
803+ print ! ( "> " ) ;
804+ return ;
805+ }
806+ } else {
807+ if required_amount_msat. is_some ( ) && invoice. amount_milli_satoshis ( ) != required_amount_msat
808+ {
809+ println ! (
810+ "Amount didn't match invoice value of {}msat" ,
811+ invoice. amount_milli_satoshis( ) . unwrap_or( 0 )
812+ ) ;
813+ print ! ( "> " ) ;
814+ return ;
815+ }
816+ payment_parameters_from_invoice ( invoice)
817+ } ;
818+ let ( payment_hash, recipient_onion, route_params) = match pay_params_opt {
819+ Ok ( res) => res,
820+ Err ( e) => {
821+ println ! ( "Failed to parse invoice" ) ;
822+ print ! ( "> " ) ;
823+ return ;
824+ }
825+ } ;
695826 outbound_payments. payments . insert (
696827 payment_id,
697828 PaymentInfo {
@@ -703,42 +834,6 @@ fn send_payment(
703834 ) ;
704835 fs_store. write ( "" , "" , OUTBOUND_PAYMENTS_FNAME , & outbound_payments. encode ( ) ) . unwrap ( ) ;
705836
706- let mut recipient_onion = RecipientOnionFields :: secret_only ( * invoice. payment_secret ( ) ) ;
707- recipient_onion. payment_metadata = invoice. payment_metadata ( ) . map ( |v| v. clone ( ) ) ;
708- let mut payment_params = match PaymentParameters :: from_node_id (
709- invoice. recover_payee_pub_key ( ) ,
710- invoice. min_final_cltv_expiry_delta ( ) as u32 ,
711- )
712- . with_expiry_time (
713- invoice. duration_since_epoch ( ) . as_secs ( ) . saturating_add ( invoice. expiry_time ( ) . as_secs ( ) ) ,
714- )
715- . with_route_hints ( invoice. route_hints ( ) )
716- {
717- Err ( e) => {
718- println ! ( "ERROR: Could not process invoice to prepare payment parameters, {:?}, invoice: {:?}" , e, invoice) ;
719- return ;
720- }
721- Ok ( p) => p,
722- } ;
723- if let Some ( features) = invoice. features ( ) {
724- payment_params = match payment_params. with_bolt11_features ( features. clone ( ) ) {
725- Err ( e) => {
726- println ! ( "ERROR: Could not build BOLT11 payment parameters! {:?}" , e) ;
727- return ;
728- }
729- Ok ( p) => p,
730- } ;
731- }
732- let invoice_amount = match invoice. amount_milli_satoshis ( ) {
733- None => {
734- println ! ( "ERROR: An invoice with an amount is expected; {:?}" , invoice) ;
735- return ;
736- }
737- Some ( a) => a,
738- } ;
739- let route_params =
740- RouteParameters :: from_payment_params_and_value ( payment_params, invoice_amount) ;
741-
742837 match channel_manager. send_payment (
743838 payment_hash,
744839 recipient_onion,
0 commit comments