@@ -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 ;
@@ -73,7 +74,7 @@ pub(crate) fn poll_for_user_input(
7374 ) ;
7475 println ! ( "LDK logs are available at <your-supplied-ldk-data-dir-path>/.ldk/logs" ) ;
7576 println ! ( "Local Node ID is {}." , channel_manager. get_our_node_id( ) ) ;
76- loop {
77+ ' read_command : loop {
7778 print ! ( "> " ) ;
7879 io:: stdout ( ) . flush ( ) . unwrap ( ) ; // Without flushing, the `>` doesn't print
7980 let mut line = String :: new ( ) ;
@@ -161,20 +162,73 @@ pub(crate) fn poll_for_user_input(
161162 continue ;
162163 }
163164
164- let invoice = match Bolt11Invoice :: from_str ( invoice_str. unwrap ( ) ) {
165- Ok ( inv) => inv,
166- Err ( e) => {
167- println ! ( "ERROR: invalid invoice: {:?}" , e) ;
168- continue ;
165+ if let Ok ( offer) = Offer :: from_str ( invoice_str. unwrap ( ) ) {
166+ let offer_hash = Sha256 :: hash ( invoice_str. unwrap ( ) . as_bytes ( ) ) ;
167+ let payment_id = PaymentId ( * offer_hash. as_ref ( ) ) ;
168+
169+ let amt_msat =
170+ match offer. amount ( ) {
171+ Some ( offer:: Amount :: Bitcoin { amount_msats } ) => * amount_msats,
172+ amt => {
173+ println ! ( "ERROR: Cannot process non-Bitcoin-denominated offer value {:?}" , amt) ;
174+ continue ;
175+ }
176+ } ;
177+
178+ loop {
179+ print ! ( "Paying offer for {} msat. Continue (Y/N)? >" , amt_msat) ;
180+ io:: stdout ( ) . flush ( ) . unwrap ( ) ;
181+
182+ if let Err ( e) = io:: stdin ( ) . read_line ( & mut line) {
183+ println ! ( "ERROR: {}" , e) ;
184+ break ' read_command;
185+ }
186+
187+ if line. len ( ) == 0 {
188+ // We hit EOF / Ctrl-D
189+ break ' read_command;
190+ }
191+
192+ if line. starts_with ( "Y" ) {
193+ break ;
194+ }
195+ if line. starts_with ( "N" ) {
196+ continue ' read_command;
197+ }
169198 }
170- } ;
171199
172- send_payment (
173- & channel_manager,
174- & invoice,
175- & mut outbound_payments. lock ( ) . unwrap ( ) ,
176- Arc :: clone ( & fs_store) ,
177- ) ;
200+ outbound_payments. lock ( ) . unwrap ( ) . payments . insert (
201+ payment_id,
202+ PaymentInfo {
203+ preimage : None ,
204+ secret : None ,
205+ status : HTLCStatus :: Pending ,
206+ amt_msat : MillisatAmount ( Some ( amt_msat) ) ,
207+ } ,
208+ ) ;
209+ fs_store
210+ . write ( "" , "" , OUTBOUND_PAYMENTS_FNAME , & outbound_payments. encode ( ) )
211+ . unwrap ( ) ;
212+
213+ let retry = Retry :: Timeout ( Duration :: from_secs ( 10 ) ) ;
214+ let pay = channel_manager
215+ . pay_for_offer ( & offer, None , None , None , payment_id, retry, None ) ;
216+ if pay. is_err ( ) {
217+ println ! ( "ERROR: Failed to pay: {:?}" , pay) ;
218+ }
219+ } else {
220+ match Bolt11Invoice :: from_str ( invoice_str. unwrap ( ) ) {
221+ Ok ( invoice) => send_payment (
222+ & channel_manager,
223+ & invoice,
224+ & mut outbound_payments. lock ( ) . unwrap ( ) ,
225+ Arc :: clone ( & fs_store) ,
226+ ) ,
227+ Err ( e) => {
228+ println ! ( "ERROR: invalid invoice: {:?}" , e) ;
229+ }
230+ }
231+ }
178232 }
179233 "keysend" => {
180234 let dest_pubkey = match words. next ( ) {
@@ -213,6 +267,34 @@ pub(crate) fn poll_for_user_input(
213267 Arc :: clone ( & fs_store) ,
214268 ) ;
215269 }
270+ "getoffer" => {
271+ let offer_builder = channel_manager. create_offer_builder ( String :: new ( ) ) ;
272+ if let Err ( e) = offer_builder {
273+ println ! ( "ERROR: Failed to initiate offer building: {:?}" , e) ;
274+ continue ;
275+ }
276+
277+ let amt_str = words. next ( ) ;
278+ let offer = if amt_str. is_some ( ) {
279+ let amt_msat: Result < u64 , _ > = amt_str. unwrap ( ) . parse ( ) ;
280+ if amt_msat. is_err ( ) {
281+ println ! ( "ERROR: getoffer provided payment amount was not a number" ) ;
282+ continue ;
283+ }
284+ offer_builder. unwrap ( ) . amount_msats ( amt_msat. unwrap ( ) ) . build ( )
285+ } else {
286+ offer_builder. unwrap ( ) . build ( )
287+ } ;
288+
289+ if offer. is_err ( ) {
290+ println ! ( "ERROR: Failed to build offer: {:?}" , offer. unwrap_err( ) ) ;
291+ } else {
292+ // Note that unlike BOLT11 invoice creation we don't bother to add a
293+ // pending inbound payment here, as offers can be reused and don't
294+ // correspond with individual payments.
295+ println ! ( "{}" , offer. unwrap( ) ) ;
296+ }
297+ }
216298 "getinvoice" => {
217299 let amt_str = words. next ( ) ;
218300 if amt_str. is_none ( ) {
@@ -481,11 +563,12 @@ fn help() {
481563 println ! ( " disconnectpeer <peer_pubkey>" ) ;
482564 println ! ( " listpeers" ) ;
483565 println ! ( "\n Payments:" ) ;
484- println ! ( " sendpayment <invoice>" ) ;
566+ println ! ( " sendpayment <invoice|offer >" ) ;
485567 println ! ( " keysend <dest_pubkey> <amt_msats>" ) ;
486568 println ! ( " listpayments" ) ;
487569 println ! ( "\n Invoices:" ) ;
488570 println ! ( " getinvoice <amt_msats> <expiry_secs>" ) ;
571+ println ! ( " getoffer [<amt_msats>]" ) ;
489572 println ! ( "\n Other:" ) ;
490573 println ! ( " signmessage <message>" ) ;
491574 println ! (
0 commit comments