@@ -11,6 +11,7 @@ use bitcoin::secp256k1::PublicKey;
11
11
use lightning:: ln:: channelmanager:: { PaymentId , RecipientOnionFields , Retry } ;
12
12
use lightning:: ln:: msgs:: SocketAddress ;
13
13
use lightning:: ln:: { ChannelId , PaymentHash , PaymentPreimage } ;
14
+ use lightning:: offers:: offer:: { self , Offer } ;
14
15
use lightning:: onion_message:: messenger:: Destination ;
15
16
use lightning:: onion_message:: packet:: OnionMessageContents ;
16
17
use lightning:: routing:: gossip:: NodeId ;
@@ -73,7 +74,7 @@ pub(crate) fn poll_for_user_input(
73
74
) ;
74
75
println ! ( "LDK logs are available at <your-supplied-ldk-data-dir-path>/.ldk/logs" ) ;
75
76
println ! ( "Local Node ID is {}." , channel_manager. get_our_node_id( ) ) ;
76
- loop {
77
+ ' read_command : loop {
77
78
print ! ( "> " ) ;
78
79
io:: stdout ( ) . flush ( ) . unwrap ( ) ; // Without flushing, the `>` doesn't print
79
80
let mut line = String :: new ( ) ;
@@ -161,20 +162,73 @@ pub(crate) fn poll_for_user_input(
161
162
continue ;
162
163
}
163
164
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
+ }
169
198
}
170
- } ;
171
199
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
+ }
178
232
}
179
233
"keysend" => {
180
234
let dest_pubkey = match words. next ( ) {
@@ -213,6 +267,34 @@ pub(crate) fn poll_for_user_input(
213
267
Arc :: clone ( & fs_store) ,
214
268
) ;
215
269
}
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
+ }
216
298
"getinvoice" => {
217
299
let amt_str = words. next ( ) ;
218
300
if amt_str. is_none ( ) {
@@ -481,11 +563,12 @@ fn help() {
481
563
println ! ( " disconnectpeer <peer_pubkey>" ) ;
482
564
println ! ( " listpeers" ) ;
483
565
println ! ( "\n Payments:" ) ;
484
- println ! ( " sendpayment <invoice>" ) ;
566
+ println ! ( " sendpayment <invoice|offer >" ) ;
485
567
println ! ( " keysend <dest_pubkey> <amt_msats>" ) ;
486
568
println ! ( " listpayments" ) ;
487
569
println ! ( "\n Invoices:" ) ;
488
570
println ! ( " getinvoice <amt_msats> <expiry_secs>" ) ;
571
+ println ! ( " getoffer [<amt_msats>]" ) ;
489
572
println ! ( "\n Other:" ) ;
490
573
println ! ( " signmessage <message>" ) ;
491
574
println ! (
0 commit comments