diff --git a/src/cli.rs b/src/cli.rs index ac28fee..1d5b5f4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -15,6 +15,36 @@ use std::path::PathBuf; use std::process::exit; use tonic::transport::{Certificate, Channel, ClientTlsConfig}; use tonic::Request; +use tonic_types::StatusExt; + +trait ExitGracefully { + fn exit_gracefully(self) -> !; +} + +impl ExitGracefully for &str { + fn exit_gracefully(self) -> ! { + println!("{self}"); + exit(1) + } +} + +impl ExitGracefully for tonic::Status { + fn exit_gracefully(self) -> ! { + let details = self.get_error_details(); + let error_msg = if let Some(error_info) = details.error_info() { + format!("ERROR ({}): {}", error_info.reason, self.message()) + } else { + format!("ERROR: {}", self.message()) + }; + error_msg.exit_gracefully(); + } +} + +impl ExitGracefully for lndk::offers::OfferError { + fn exit_gracefully(self) -> ! { + format!("ERROR ({}): {}", self.code(), self).exit_gracefully(); + } +} fn get_macaroon_path_default(network: &str) -> PathBuf { home::home_dir() @@ -174,17 +204,8 @@ async fn main() { Commands::DecodeOffer { offer_string } => { println!("Decoding offer: {offer_string}."); match decode(offer_string) { - Ok(offer) => { - println!("Decoded offer: {:?}.", offer) - } - Err(e) => { - println!( - "ERROR please provide offer starting with lno. Provided offer is \ - invalid, failed to decode with error: {:?}.", - e - ); - exit(1) - } + Ok(offer) => println!("Decoded offer: {:?}.", offer), + Err(e) => e.exit_gracefully(), } } Commands::DecodeInvoice { invoice_string } => { @@ -195,14 +216,7 @@ async fn main() { Ok(invoice) => { println!("Decoded invoice: {:?}.", invoice); } - Err(e) => { - println!( - "ERROR please provide hex-encoded invoice string. Provided invoice is \ - invalid, failed to decode with error: {:?}.", - e - ); - exit(1); - } + Err(e) => e.exit_gracefully(), } } Commands::PayOffer { @@ -214,41 +228,19 @@ async fn main() { fee_limit_percent, } => { let tls = read_cert_from_args_or_exit(args.cert_pem, args.cert_path); - let grpc_host = args.grpc_host; - let grpc_port = args.grpc_port; - let channel = Channel::from_shared(format!("{grpc_host}:{grpc_port}")) - .unwrap_or_else(|e| { - println!("ERROR creating endpoint: {e:?}"); - exit(1) - }) - .tls_config(tls) - .unwrap_or_else(|e| { - println!("ERROR tls config: {e:?}"); - exit(1) - }) - .connect() - .await - .unwrap_or_else(|e| { - println!("ERROR connecting: {e:?}"); - exit(1) - }); + let channel = create_grpc_channel(args.grpc_host, args.grpc_port, tls).await; + let (mut client, macaroon) = create_authenticated_client( + channel, + args.macaroon_path, + args.macaroon_hex, + &args.network, + ); - let mut client = OffersClient::new(channel); - - let offer = match decode(offer_string.to_owned()) { + let offer = match decode(offer_string.to_string()) { Ok(offer) => offer, - Err(e) => { - println!( - "ERROR: please provide offer starting with lno. Provided offer is \ - invalid, failed to decode with error: {:?}.", - e - ); - exit(1) - } + Err(e) => e.exit_gracefully(), }; - let macaroon = - read_macaroon_from_args(args.macaroon_path, args.macaroon_hex, &args.network); let mut request = Request::new(PayOfferRequest { offer: offer.to_string(), amount, @@ -257,14 +249,11 @@ async fn main() { fee_limit, fee_limit_percent, }); - add_metadata(&mut request, macaroon).unwrap_or_else(|_| exit(1)); + add_metadata(&mut request, macaroon); match client.pay_offer(request).await { Ok(_) => println!("Successfully paid for offer!"), - Err(err) => { - println!("Error paying for offer: {err:?}"); - exit(1) - } + Err(err) => err.exit_gracefully(), }; } Commands::GetInvoice { @@ -274,55 +263,29 @@ async fn main() { response_invoice_timeout, } => { let tls = read_cert_from_args_or_exit(args.cert_pem, args.cert_path); - let grpc_host = args.grpc_host; - let grpc_port = args.grpc_port; - let channel = Channel::from_shared(format!("{grpc_host}:{grpc_port}")) - .unwrap_or_else(|e| { - println!("ERROR creating endpoint: {e:?}"); - exit(1) - }) - .tls_config(tls) - .unwrap_or_else(|e| { - println!("ERROR tls config: {e:?}"); - exit(1) - }) - .connect() - .await - .unwrap_or_else(|e| { - println!("ERROR connecting: {e:?}"); - exit(1) - }); + let channel = create_grpc_channel(args.grpc_host, args.grpc_port, tls).await; + let (mut client, macaroon) = create_authenticated_client( + channel, + args.macaroon_path, + args.macaroon_hex, + &args.network, + ); - let mut client = OffersClient::new(channel); - let offer = match decode(offer_string.to_owned()) { + let offer = match decode(offer_string.to_string()) { Ok(offer) => offer, - Err(e) => { - println!( - "ERROR: please provide offer starting with lno. Provided offer is \ - invalid, failed to decode with error: {:?}.", - e - ); - exit(1) - } + Err(e) => e.exit_gracefully(), }; - let macaroon = - read_macaroon_from_args(args.macaroon_path, args.macaroon_hex, &args.network); let mut request = Request::new(GetInvoiceRequest { offer: offer.to_string(), amount, payer_note, response_invoice_timeout, }); - add_metadata(&mut request, macaroon).unwrap_or_else(|_| exit(1)); + add_metadata(&mut request, macaroon); match client.get_invoice(request).await { - Ok(response) => { - println!("Invoice: {:?}.", response.get_ref()) - } - Err(err) => { - println!("Error getting invoice for offer: {err:?}"); - exit(1) - } + Ok(response) => println!("Invoice: {:?}.", response.get_ref()), + Err(err) => err.exit_gracefully(), } } Commands::PayInvoice { @@ -332,41 +295,24 @@ async fn main() { fee_limit_percent, } => { let tls = read_cert_from_args_or_exit(args.cert_pem, args.cert_path); - let grpc_host = args.grpc_host.clone(); - let grpc_port = args.grpc_port; - let channel = Channel::from_shared(format!("{grpc_host}:{grpc_port}")) - .unwrap_or_else(|e| { - println!("ERROR creating endpoint: {e:?}"); - exit(1) - }) - .tls_config(tls) - .unwrap_or_else(|e| { - println!("ERROR tls config: {e:?}"); - exit(1) - }) - .connect() - .await - .unwrap_or_else(|e| { - println!("ERROR connecting: {e:?}"); - exit(1) - }); + let channel = create_grpc_channel(args.grpc_host, args.grpc_port, tls).await; + let (mut client, macaroon) = create_authenticated_client( + channel, + args.macaroon_path, + args.macaroon_hex, + &args.network, + ); - let mut client = OffersClient::new(channel); - let macaroon = - read_macaroon_from_args(args.macaroon_path, args.macaroon_hex, &args.network); let mut request = Request::new(PayInvoiceRequest { invoice: invoice_string.to_owned(), amount, fee_limit, fee_limit_percent, }); - add_metadata(&mut request, macaroon).unwrap_or_else(|_| exit(1)); + add_metadata(&mut request, macaroon); match client.pay_invoice(request).await { Ok(_) => println!("Successfully paid for offer!"), - Err(err) => { - println!("Error paying invoice: {err:?}"); - exit(1) - } + Err(err) => err.exit_gracefully(), } } Commands::CreateOffer { @@ -377,28 +323,14 @@ async fn main() { quantity, } => { let tls = read_cert_from_args_or_exit(args.cert_pem, args.cert_path); - let grpc_host = args.grpc_host.clone(); - let grpc_port = args.grpc_port; - let channel = Channel::from_shared(format!("{grpc_host}:{grpc_port}")) - .unwrap_or_else(|e| { - println!("ERROR creating endpoint: {e:?}"); - exit(1) - }) - .tls_config(tls) - .unwrap_or_else(|e| { - println!("ERROR tls config: {e:?}"); - exit(1) - }) - .connect() - .await - .unwrap_or_else(|e| { - println!("ERROR connecting: {e:?}"); - exit(1) - }); + let channel = create_grpc_channel(args.grpc_host, args.grpc_port, tls).await; + let (mut client, macaroon) = create_authenticated_client( + channel, + args.macaroon_path, + args.macaroon_hex, + &args.network, + ); - let mut client = OffersClient::new(channel); - let macaroon = - read_macaroon_from_args(args.macaroon_path, args.macaroon_hex, &args.network); let mut request = Request::new(CreateOfferRequest { amount, quantity, @@ -406,27 +338,42 @@ async fn main() { issuer, expiry, }); - add_metadata(&mut request, macaroon).unwrap_or_else(|_| exit(1)); + add_metadata(&mut request, macaroon); match client.create_offer(request).await { - Ok(response) => { - println!("Offer: {:?}.", response.get_ref()) - } - Err(err) => { - println!("Error creating offer: {err:?}"); - exit(1) - } + Ok(response) => println!("Offer: {:?}.", response.get_ref()), + Err(err) => err.exit_gracefully(), } } } } -fn add_metadata(request: &mut Request, macaroon: String) -> Result<(), ()> { - let macaroon = macaroon.parse().map_err(|e| { - println!("Error parsing provided macaroon string into tonic metadata {e:?}") - })?; - request.metadata_mut().insert("macaroon", macaroon); +async fn create_grpc_channel(grpc_host: String, grpc_port: u16, tls: ClientTlsConfig) -> Channel { + Channel::from_shared(format!("{grpc_host}:{grpc_port}")) + .unwrap_or_else(|e| format!("ERROR: failed to create endpoint {e:?}").exit_gracefully()) + .tls_config(tls) + .unwrap_or_else(|e| format!("ERROR: failed to configure tls {e:?}").exit_gracefully()) + .connect() + .await + .unwrap_or_else(|e| format!("ERROR: failed to connect {e:?}").exit_gracefully()) +} - Ok(()) +fn create_authenticated_client( + channel: Channel, + macaroon_path: Option, + macaroon_hex: Option, + network: &str, +) -> (OffersClient, String) { + let client = OffersClient::new(channel); + let macaroon = read_macaroon_from_args(macaroon_path, macaroon_hex, network); + (client, macaroon) +} + +fn add_metadata(request: &mut Request, macaroon: String) { + let macaroon = macaroon.parse().unwrap_or_else(|e| { + format!("ERROR: failed to parse provided macaroon string into tonic metadata {e:?}") + .exit_gracefully() + }); + request.metadata_mut().insert("macaroon", macaroon); } fn read_macaroon_from_file(path: PathBuf) -> Result { @@ -450,7 +397,7 @@ fn read_cert_from_args( let pem = match (&cert_pem, &cert_path) { (Some(pem), _) => pem.clone(), (None, Some(cert_path)) => std::fs::read_to_string(cert_path) - .map_err(|e| format!("ERROR reading cert: {:?}", e))?, + .map_err(|e| format!("ERROR: failed to read cert: {:?}", e))?, (None, None) => { // If no cert pem string is provided, we'll look for the tls certificate in the // default location. @@ -459,7 +406,7 @@ fn read_cert_from_args( .join(DEFAULT_LNDK_DIR) .join(DEFAULT_DATA_DIR); std::fs::read_to_string(data_dir.join(TLS_CERT_FILENAME)) - .map_err(|e| format!("ERROR reading cert: {:?}", e))? + .map_err(|e| format!("ERROR: failed to read cert: {:?}", e))? } }; let cert = Certificate::from_pem(pem); @@ -474,10 +421,7 @@ fn read_cert_from_args_or_exit( ) -> ClientTlsConfig { match read_cert_from_args(cert_pem, cert_path) { Ok(config) => config, - Err(err) => { - println!("{}", err); - exit(1); - } + Err(err) => err.exit_gracefully(), } } @@ -488,24 +432,21 @@ fn read_macaroon_from_args( ) -> String { // Make sure both macaroon options are not set. if macaroon_path.is_some() && macaroon_hex.is_some() { - println!("ERROR: Only one of `macaroon_path` or `macaroon_hex` should be set."); - exit(1) + "ERROR: Only one of `macaroon_path` or `macaroon_hex` should be set.".exit_gracefully(); } // Let's grab the macaroon string now. If neither macaroon_path nor macaroon_hex are // set, use the default macaroon path. match macaroon_path { Some(path) => read_macaroon_from_file(path.clone()).unwrap_or_else(|e| { - println!("ERROR reading macaroon from file {e:?}"); - exit(1) + format!("ERROR: failed to read macaroon from file {e:?}").exit_gracefully() }), None => match &macaroon_hex { Some(macaroon) => macaroon.clone(), None => { let path = get_macaroon_path_default(network); read_macaroon_from_file(path).unwrap_or_else(|e| { - println!("ERROR reading macaroon from file {e:?}"); - exit(1) + format!("ERROR: failed to read macaroon from file {e:?}").exit_gracefully() }) } }, @@ -568,7 +509,9 @@ mod tests { let result = read_cert_from_args(None, Some(invalid_path)); assert!(result.is_err()); - assert!(result.unwrap_err().starts_with("ERROR reading cert:")); + assert!(result + .unwrap_err() + .starts_with("ERROR: failed to read cert:")); } #[test] diff --git a/src/lnd.rs b/src/lnd.rs index 66c84f8..3731bc3 100644 --- a/src/lnd.rs +++ b/src/lnd.rs @@ -373,7 +373,7 @@ pub enum LndError { /// LND node is not connected to bitcoin network. NetworkNotConnected, /// LND service is unavailable or not responding. - ServiceUnavailable(LndStatus), + ServiceUnavailable(String), } impl LndError { diff --git a/src/offers/client_impls.rs b/src/offers/client_impls.rs index b628f86..818d28b 100644 --- a/src/offers/client_impls.rs +++ b/src/offers/client_impls.rs @@ -189,11 +189,15 @@ impl InvoicePayer for Client { .router() .track_payment_v2(req) .await - .map_err(OfferError::TrackFailure)? + .map_err(|e| OfferError::TrackFailure(e.message().to_string()))? .into_inner(); // Wait for a failed or successful payment. - while let Some(payment) = stream.message().await.map_err(OfferError::TrackFailure)? { + while let Some(payment) = stream + .message() + .await + .map_err(|e| OfferError::TrackFailure(e.message().to_string()))? + { if payment.status() == tonic_lnd::lnrpc::payment::PaymentStatus::Succeeded { return Ok(payment); } else if payment.status() == tonic_lnd::lnrpc::payment::PaymentStatus::Failed { diff --git a/src/offers/lnd_requests.rs b/src/offers/lnd_requests.rs index 5d02bd8..643f722 100644 --- a/src/offers/lnd_requests.rs +++ b/src/offers/lnd_requests.rs @@ -122,13 +122,13 @@ pub(crate) async fn send_payment( params.fee_limit, ) .await - .map_err(OfferError::RouteFailure)?; + .map_err(|e| OfferError::RouteFailure(e.message().to_string()))?; trace!("Routes found {}...", resp.routes.len()); let resp = payer .send_to_route(params.payment_hash, resp.routes[0].clone()) .await - .map_err(OfferError::RouteFailure)?; + .map_err(|e| OfferError::RouteFailure(e.message().to_string()))?; trace!( "Sent payment using preimage {} using attempt_id {} with status {}. {}", @@ -253,13 +253,13 @@ pub(super) async fn create_invoice_info_from_request( let invoice_response = creator .add_invoice(invoice_request) .await - .map_err(OfferError::AddInvoiceFailure)?; + .map_err(|e| OfferError::AddInvoiceFailure(e.message().to_string()))?; let payment_request = invoice_response.payment_request; log::trace!("Payment request: {:?}", payment_request); let payreq = creator .decode_payment_request(payment_request) .await - .map_err(OfferError::DecodePaymentRequestFailure)?; + .map_err(|e| OfferError::DecodePaymentRequestFailure(e.message().to_string()))?; log::trace!("Decoded payment request: {:?}", payreq); let payment_hash = bitcoin::hashes::sha256::Hash::from_str(&payreq.payment_hash).map_err(|e| { @@ -290,7 +290,7 @@ pub async fn create_reply_path_for_offer_creation( // Find introduction channels for our blinded paths. let current_channels = connector.list_active_public_channels().await.map_err(|e| { error!("Could not lookup current channels: {e}."); - OfferError::ListChannelsFailure(e) + OfferError::ListChannelsFailure(e.message().to_string()) })?; let mut intro_channels = HashSet::new(); @@ -376,7 +376,7 @@ pub async fn create_reply_path_for_outgoing_payments( // Find an introduction node for our blinded path. let current_peers = connector.list_peers().await.map_err(|e| { error!("Could not lookup current peers: {e}."); - OfferError::ListPeersFailure(e) + OfferError::ListPeersFailure(e.message().to_string()) })?; let mut intro_node = None; @@ -535,7 +535,7 @@ pub(crate) async fn connect_to_peer( let resp = connector .list_peers() .await - .map_err(OfferError::PeerConnectError)?; + .map_err(|e| OfferError::PeerConnectError(e.message().to_string()))?; let node_id_str = node_id.to_string(); for peer in resp.peers.iter() { @@ -551,7 +551,7 @@ pub(crate) async fn connect_to_peer( let node = connector .get_node_info(node_id_str.clone(), false) .await - .map_err(OfferError::PeerConnectError)?; + .map_err(|e| OfferError::PeerConnectError(e.message().to_string()))?; let node = match node.node { Some(node) => node, @@ -565,7 +565,7 @@ pub(crate) async fn connect_to_peer( connector .connect_peer(node_id_str, node.addresses[0].clone().addr) .await - .map_err(OfferError::PeerConnectError)?; + .map_err(|e| OfferError::PeerConnectError(e.message().to_string()))?; Ok(()) } @@ -596,7 +596,7 @@ pub(super) async fn get_node_id_from_scid( .lightning_read_only() .get_chan_info(get_info_request) .await - .map_err(OfferError::GetChannelInfo)? + .map_err(|e| OfferError::GetChannelInfo(e.message().to_string()))? .into_inner(); match direction { Direction::NodeOne => Ok(channel_info.node1_pub), diff --git a/src/offers/mod.rs b/src/offers/mod.rs index 07c8dae..81608d7 100644 --- a/src/offers/mod.rs +++ b/src/offers/mod.rs @@ -6,13 +6,12 @@ use lightning::{ offers::{merkle::SignError, parse::Bolt12ParseError, parse::Bolt12SemanticError}, }; use tonic::{Code, Status}; -use tonic_lnd::tonic::Status as LndStatus; use tonic_types::{ErrorDetails, StatusExt}; mod client_impls; pub mod handler; mod lnd_requests; -mod parse; +pub mod parse; pub(crate) use lnd_requests::connect_to_peer_with_retry; pub use lnd_requests::create_reply_path_for_offer_creation; @@ -29,23 +28,23 @@ pub enum OfferError { /// SignError indicates a failure to sign the invoice request. SignError(SignError), /// DeriveKeyFailure indicates a failure to derive key for signing the invoice request. - DeriveKeyFailure(LndStatus), + DeriveKeyFailure(String), /// User provided an invalid amount. InvalidAmount(String), /// Invalid currency contained in the offer. InvalidCurrency, /// Unable to connect to peer. - PeerConnectError(LndStatus), + PeerConnectError(String), /// No node address. NodeAddressNotFound, /// Cannot list peers. - ListPeersFailure(LndStatus), + ListPeersFailure(String), /// Failure to build a reply path. BuildBlindedPathFailure, /// Unable to find or send to payment route. - RouteFailure(LndStatus), + RouteFailure(String), /// Failed to track payment. - TrackFailure(LndStatus), + TrackFailure(String), /// Failed to send payment. PaymentFailure, /// Failed to receive an invoice back from offer creator before the timeout. @@ -53,15 +52,15 @@ pub enum OfferError { /// Failed to find introduction node for blinded path. IntroductionNodeNotFound, /// Cannot fetch channel info. - GetChannelInfo(LndStatus), + GetChannelInfo(String), /// Failed to create offer. CreateOfferFailure(Bolt12SemanticError), /// Failed to create offer with expiry time given system clock. CreateOfferTimeFailure, /// Failed to add invoice. - AddInvoiceFailure(LndStatus), + AddInvoiceFailure(String), /// Failed to decode payment request. - DecodePaymentRequestFailure(LndStatus), + DecodePaymentRequestFailure(String), /// Failed to parse payment hash. ParsePaymentHashFailure(String), /// Failed to parse offer. @@ -71,7 +70,7 @@ pub enum OfferError { /// Failed to encode invoice. EncodeInvoiceFailure(BitcoinIoError), /// Cannot list channels. - ListChannelsFailure(LndStatus), + ListChannelsFailure(String), } impl OfferError { @@ -131,53 +130,44 @@ impl Display for OfferError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { OfferError::AlreadyProcessing(id) => { - write!( - f, - "We're already trying to pay for a payment with this id {id}" - ) + write!(f, "Payment with id {id} already in progress") } - OfferError::BuildUIRFailure(e) => write!(f, "Error building invoice request: {e:?}"), - OfferError::SignError(e) => write!(f, "Error signing invoice request: {e:?}"), - OfferError::DeriveKeyFailure(e) => write!(f, "Error signing invoice request: {e:?}"), - OfferError::InvalidAmount(e) => write!(f, "User provided an invalid amount: {e:?}"), - OfferError::InvalidCurrency => write!( - f, - "LNDK doesn't yet support offer currencies other than bitcoin" - ), - OfferError::PeerConnectError(e) => write!(f, "Error connecting to peer: {e:?}"), - OfferError::NodeAddressNotFound => write!(f, "Couldn't get node address"), - OfferError::ListPeersFailure(e) => write!(f, "Error listing peers: {e:?}"), - OfferError::BuildBlindedPathFailure => write!(f, "Error building blinded path"), - OfferError::RouteFailure(e) => write!(f, "Error routing payment: {e:?}"), - OfferError::TrackFailure(e) => write!(f, "Error tracking payment: {e:?}"), - OfferError::PaymentFailure => write!(f, "Failed to send payment"), - OfferError::InvoiceTimeout(e) => write!(f, "Did not receive invoice in {e:?} seconds."), - OfferError::IntroductionNodeNotFound => write!(f, "Could not find introduction node."), - OfferError::GetChannelInfo(e) => write!(f, "Could not fetch channel info: {e:?}"), - OfferError::CreateOfferFailure(e) => write!(f, "Could not create offer: {e:?}"), - OfferError::CreateOfferTimeFailure => write!( - f, - "Could not create offer with expiry time given system clock" - ), + OfferError::BuildUIRFailure(e) => write!(f, "Failed to build invoice request: {e:?}"), + OfferError::SignError(e) => write!(f, "Failed to sign invoice request: {e:?}"), + OfferError::DeriveKeyFailure(e) => write!(f, "Failed to derive key: {e}"), + OfferError::InvalidAmount(e) => write!(f, "Invalid amount: {e}"), + OfferError::InvalidCurrency => write!(f, "Only bitcoin currency is supported"), + OfferError::PeerConnectError(e) => write!(f, "Failed to connect to peer: {e}"), + OfferError::NodeAddressNotFound => write!(f, "Node address not found"), + OfferError::ListPeersFailure(e) => write!(f, "Failed to list peers: {e}"), + OfferError::BuildBlindedPathFailure => write!(f, "Failed to build blinded path"), + OfferError::RouteFailure(e) => write!(f, "Failed to route payment: {e}"), + OfferError::TrackFailure(e) => write!(f, "Failed to track payment: {e}"), + OfferError::PaymentFailure => write!(f, "Payment failed"), + OfferError::InvoiceTimeout(e) => { + write!(f, "Invoice request timed out after {e} seconds") + } + OfferError::IntroductionNodeNotFound => write!(f, "Introduction node not found"), + OfferError::GetChannelInfo(e) => write!(f, "Failed to get channel info: {e}"), + OfferError::CreateOfferFailure(e) => write!(f, "Failed to create offer: {e:?}"), + OfferError::CreateOfferTimeFailure => write!(f, "Invalid offer expiry time"), OfferError::AddInvoiceFailure(e) => { - write!(f, "Could not add invoice to lnd node: {e:?}") + write!(f, "Failed to add invoice to lnd node: {e}") } OfferError::DecodePaymentRequestFailure(e) => { - write!(f, "Could not decode payment request: {e:?}") + write!(f, "Failed to decode payment request: {e}") } OfferError::ParsePaymentHashFailure(e) => { - write!(f, "Could not parse payment hash: {e:?}") + write!(f, "Failed to parse payment hash: {e:?}") } OfferError::ParseOfferFailure(e) => { - write!(f, "The provided offer was invalid. Please provide a valid offer in bech32 format, i.e. starting with 'lno'. Error: {e:?}") + write!(f, "Invalid offer: must start with 'lno'. Error: {e:?}") } OfferError::ParseInvoiceFailure(e) => { - write!(f, "The provided invoice was invalid. Please provide a valid invoice in hex format. Error: {e:?}") - } - OfferError::EncodeInvoiceFailure(e) => { - write!(f, "Failed to encode invoice to hex format. Error: {e:?}") + write!(f, "Invalid invoice: must be hex format. Error: {e:?}") } OfferError::ListChannelsFailure(e) => write!(f, "Error listing channels: {e:?}"), + OfferError::EncodeInvoiceFailure(e) => write!(f, "Failed to encode invoice: {e:?}"), } } } diff --git a/src/offers/parse.rs b/src/offers/parse.rs index 2b95f75..de6bbb9 100644 --- a/src/offers/parse.rs +++ b/src/offers/parse.rs @@ -1,16 +1,14 @@ use lightning::{ - offers::{ - offer::{Amount, Offer}, - parse::Bolt12ParseError, - }, + offers::offer::{Amount, Offer}, onion_message::messenger::Destination, }; +use std::str::FromStr; use super::OfferError; /// Decodes a bech32 offer string into an LDK offer. -pub fn decode(offer_str: String) -> Result { - offer_str.parse::() +pub fn decode(offer_str: String) -> Result { + Offer::from_str(&offer_str).map_err(OfferError::ParseOfferFailure) } /// Get the destination of an offer. diff --git a/src/server.rs b/src/server.rs index 136fe16..1404748 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,6 +2,7 @@ use crate::lnd::{get_lnd_client, get_network, Creds, LndCfg, LndError}; use crate::lndkrpc::{CreateOfferRequest, CreateOfferResponse}; use crate::offers::get_destination; use crate::offers::handler::{CreateOfferParams, PayOfferParams}; +use crate::offers::parse::decode; use crate::offers::validate_amount; use crate::offers::OfferError; use crate::{lndkrpc, Bolt12InvoiceString, OfferHandler}; @@ -10,7 +11,7 @@ use lightning::blinded_path::payment::BlindedPaymentPath; use lightning::blinded_path::{Direction, IntroductionNode}; use lightning::ln::channelmanager::PaymentId; use lightning::offers::invoice::Bolt12Invoice; -use lightning::offers::offer::{Offer, Quantity}; +use lightning::offers::offer::Quantity; use lightning::sign::EntropySource; use lightning::util::ser::Writeable; use lndkrpc::offers_server::Offers; @@ -71,7 +72,7 @@ impl Offers for LNDKServer { let mut client = get_lnd_client(lnd_cfg)?; let inner_request = request.get_ref(); - let offer = Offer::from_str(&inner_request.offer).map_err(OfferError::ParseOfferFailure)?; + let offer = decode(inner_request.offer.clone())?; let destination = get_destination(&offer).await?; let reply_path = None; @@ -79,7 +80,7 @@ impl Offers for LNDKServer { .lightning() .get_info(GetInfoRequest {}) .await - .map_err(LndError::ServiceUnavailable)? + .map_err(|e| LndError::ServiceUnavailable(e.message().to_string()))? .into_inner(); let network = get_network(info).await?; @@ -139,7 +140,7 @@ impl Offers for LNDKServer { let mut client = get_lnd_client(lnd_cfg)?; let inner_request = request.get_ref(); - let offer = Offer::from_str(&inner_request.offer).map_err(OfferError::ParseOfferFailure)?; + let offer = decode(inner_request.offer.clone())?; let destination = get_destination(&offer).await?; let reply_path = None; @@ -148,7 +149,7 @@ impl Offers for LNDKServer { .lightning() .get_info(GetInfoRequest {}) .await - .map_err(LndError::ServiceUnavailable)? + .map_err(|e| LndError::ServiceUnavailable(e.message().to_string()))? .into_inner(); let network = get_network(info).await?; @@ -238,7 +239,7 @@ impl Offers for LNDKServer { .lightning() .get_info(GetInfoRequest {}) .await - .map_err(LndError::ServiceUnavailable)? + .map_err(|e| LndError::ServiceUnavailable(e.message().to_string()))? .into_inner(); let network = get_network(info).await?; let quantity = parse_quantity(inner_request.quantity);