Merge pull request #238 from IgnacioPorte/master

Improve CLI error messages
This commit is contained in:
Maurice Poirrier 2025-09-27 10:14:41 +02:00 committed by GitHub
commit 933d68bfa8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 174 additions and 238 deletions

View file

@ -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<R>(request: &mut Request<R>, 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<PathBuf>,
macaroon_hex: Option<String>,
network: &str,
) -> (OffersClient<Channel>, String) {
let client = OffersClient::new(channel);
let macaroon = read_macaroon_from_args(macaroon_path, macaroon_hex, network);
(client, macaroon)
}
fn add_metadata<R>(request: &mut Request<R>, 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<String, std::io::Error> {
@ -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]

View file

@ -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 {

View file

@ -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 {

View file

@ -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),

View file

@ -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:?}"),
}
}
}

View file

@ -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, Bolt12ParseError> {
offer_str.parse::<Offer>()
pub fn decode(offer_str: String) -> Result<Offer, OfferError> {
Offer::from_str(&offer_str).map_err(OfferError::ParseOfferFailure)
}
/// Get the destination of an offer.

View file

@ -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);