From 93eaf27f02d699a83fa631e30a1da135e3261305 Mon Sep 17 00:00:00 2001 From: Orbital Date: Sun, 28 Jan 2024 22:50:45 -0600 Subject: [PATCH] cli: Add cli command to pay offer --- src/cli.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 7ca725cb..b6a72c51 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,11 @@ +use bitcoin::secp256k1::PublicKey; use clap::{Parser, Subcommand}; +use lndk::lnd::{get_lnd_client, string_to_network, LndCfg}; use lndk::lndk_offers::decode; +use lndk::{Cfg, LndkOnionMessenger, OfferHandler}; use std::ffi::OsString; +use std::str::FromStr; +use tokio::select; fn get_cert_path_default() -> OsString { home::home_dir() @@ -60,23 +65,104 @@ enum Commands { /// The offer string to decode. offer_string: String, }, + /// PayOffer pays a BOLT 12 offer, provided as a 'lno'-prefaced offer string. + PayOffer { + /// The offer string. + offer_string: String, + + /// Amount the user would like to pay. + amount: u64, + }, } -fn main() { +#[tokio::main] +async fn main() -> Result<(), ()> { let args = Cli::parse(); match args.command { Commands::Decode { offer_string } => { println!("Decoding offer: {offer_string}."); match decode(offer_string) { - Ok(offer) => println!("Decoded offer: {:?}.", offer), + Ok(offer) => { + println!("Decoded offer: {:?}.", offer); + Ok(()) + } Err(e) => { println!( "ERROR please provide offer starting with lno. Provided offer is \ invalid, failed to decode with error: {:?}.", e - ) + ); + Err(()) + } + } + } + Commands::PayOffer { + offer_string, + amount, + } => { + let offer = match decode(offer_string) { + Ok(offer) => offer, + Err(e) => { + println!( + "ERROR: please provide offer starting with lno. Provided offer is \ + invalid, failed to decode with error: {:?}.", + e + ); + return Err(()); + } + }; + + let network = string_to_network(&args.network).map_err(|e| { + println!("ERROR: invalid network string: {}", e); + })?; + let lnd_cfg = LndCfg::new(args.address, args.tls_cert.into(), args.macaroon.into()); + let mut client = get_lnd_client(lnd_cfg.clone()).map_err(|e| { + println!("ERROR: failed to connect to lnd: {}", e); + })?; + + let info = client + .lightning() + .get_info(tonic_lnd::lnrpc::GetInfoRequest {}) + .await + .expect("failed to get info") + .into_inner(); + let pubkey = PublicKey::from_str(&info.identity_pubkey).unwrap(); + + let (shutdown, listener) = triggered::trigger(); + let lndk_cfg = Cfg { + lnd: lnd_cfg, + log_dir: None, + shutdown: shutdown.clone(), + listener, + }; + + let handler = OfferHandler::new(); + let reply_path = handler + .create_reply_path(client.clone(), pubkey) + .await + .map_err(|e| { + println!("ERROR: failed to create reply path: {}", e); + })?; + let messenger = LndkOnionMessenger::new(handler); + select! { + _ = messenger.run(lndk_cfg) => { + println!("ERROR: lndk stopped running before pay offer finished."); + }, + // We wait for ldk2 to receive the onion message. + res = messenger.offer_handler.pay_offer( + offer.clone(), Some(amount), network, client, offer.paths()[0].clone(), + Some(reply_path), + ) => { + match res { + Ok(_) => println!("Successfully paid for offer!"), + Err(_) => println!("Error paying for offer."), + } + + shutdown.trigger(); } } + + Ok(()) } } }