diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index a45ec6e..b948681 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -10,11 +10,19 @@ use ldk_server_client::ldk_server_protos::api::{ ListChannelsRequest, ListPaymentsRequest, OnchainReceiveRequest, OnchainSendRequest, OpenChannelRequest, }; +use ldk_server_client::ldk_server_protos::types::RouteParametersConfig; use ldk_server_client::ldk_server_protos::types::{ bolt11_invoice_description, Bolt11InvoiceDescription, PageToken, Payment, }; use std::fmt::Debug; +// Having these default values as constants in the Proto file and +// importing/reusing them here might be better, but Proto3 removed +// the ability to set default values. +const DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA: u32 = 1008; +const DEFAULT_MAX_PATH_COUNT: u32 = 10; +const DEFAULT_MAX_CHANNEL_SATURATION_POWER_OF_HALF: u32 = 2; + #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Cli { @@ -55,6 +63,14 @@ enum Commands { invoice: String, #[arg(long)] amount_msat: Option, + #[arg(long)] + max_total_routing_fee_msat: Option, + #[arg(long)] + max_total_cltv_expiry_delta: Option, + #[arg(long)] + max_path_count: Option, + #[arg(long)] + max_channel_saturation_power_of_half: Option, }, Bolt12Receive { #[arg(short, long)] @@ -75,6 +91,14 @@ enum Commands { quantity: Option, #[arg(short, long)] payer_note: Option, + #[arg(long)] + max_total_routing_fee_msat: Option, + #[arg(long)] + max_total_cltv_expiry_delta: Option, + #[arg(long)] + max_path_count: Option, + #[arg(long)] + max_channel_saturation_power_of_half: Option, }, CloseChannel { #[arg(short, long)] @@ -161,9 +185,30 @@ async fn main() { handle_response_result(client.bolt11_receive(request).await); }, - Commands::Bolt11Send { invoice, amount_msat } => { + Commands::Bolt11Send { + invoice, + amount_msat, + max_total_routing_fee_msat, + max_total_cltv_expiry_delta, + max_path_count, + max_channel_saturation_power_of_half, + } => { + let route_parameters = RouteParametersConfig { + max_total_routing_fee_msat, + max_total_cltv_expiry_delta: max_total_cltv_expiry_delta + .unwrap_or(DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA), + max_path_count: max_path_count.unwrap_or(DEFAULT_MAX_PATH_COUNT), + max_channel_saturation_power_of_half: max_channel_saturation_power_of_half + .unwrap_or(DEFAULT_MAX_CHANNEL_SATURATION_POWER_OF_HALF), + }; handle_response_result( - client.bolt11_send(Bolt11SendRequest { invoice, amount_msat }).await, + client + .bolt11_send(Bolt11SendRequest { + invoice, + amount_msat, + route_parameters: Some(route_parameters), + }) + .await, ); }, Commands::Bolt12Receive { description, amount_msat, expiry_secs, quantity } => { @@ -178,10 +223,34 @@ async fn main() { .await, ); }, - Commands::Bolt12Send { offer, amount_msat, quantity, payer_note } => { + Commands::Bolt12Send { + offer, + amount_msat, + quantity, + payer_note, + max_total_routing_fee_msat, + max_total_cltv_expiry_delta, + max_path_count, + max_channel_saturation_power_of_half, + } => { + let route_parameters = RouteParametersConfig { + max_total_routing_fee_msat, + max_total_cltv_expiry_delta: max_total_cltv_expiry_delta + .unwrap_or(DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA), + max_path_count: max_path_count.unwrap_or(DEFAULT_MAX_PATH_COUNT), + max_channel_saturation_power_of_half: max_channel_saturation_power_of_half + .unwrap_or(DEFAULT_MAX_CHANNEL_SATURATION_POWER_OF_HALF), + }; + handle_response_result( client - .bolt12_send(Bolt12SendRequest { offer, amount_msat, quantity, payer_note }) + .bolt12_send(Bolt12SendRequest { + offer, + amount_msat, + quantity, + payer_note, + route_parameters: Some(route_parameters), + }) .await, ); }, diff --git a/ldk-server-protos/src/api.rs b/ldk-server-protos/src/api.rs index d89283b..631ef83 100644 --- a/ldk-server-protos/src/api.rs +++ b/ldk-server-protos/src/api.rs @@ -141,6 +141,9 @@ pub struct Bolt11SendRequest { /// This operation will fail if the amount specified is less than the value required by the given invoice. #[prost(uint64, optional, tag = "2")] pub amount_msat: ::core::option::Option, + /// Configuration options for payment routing and pathfinding. + #[prost(message, optional, tag = "3")] + pub route_parameters: ::core::option::Option, } /// The response `content` for the `Bolt11Send` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. @@ -205,6 +208,9 @@ pub struct Bolt12SendRequest { /// If set, it will be seen by the recipient and reflected back in the invoice. #[prost(string, optional, tag = "4")] pub payer_note: ::core::option::Option<::prost::alloc::string::String>, + /// Configuration options for payment routing and pathfinding. + #[prost(message, optional, tag = "5")] + pub route_parameters: ::core::option::Option, } /// The response `content` for the `Bolt12Send` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. diff --git a/ldk-server-protos/src/proto/api.proto b/ldk-server-protos/src/proto/api.proto index 32be3c3..49d4ff7 100644 --- a/ldk-server-protos/src/proto/api.proto +++ b/ldk-server-protos/src/proto/api.proto @@ -138,6 +138,9 @@ message Bolt11SendRequest { // amount paid to be determined by the user. // This operation will fail if the amount specified is less than the value required by the given invoice. optional uint64 amount_msat = 2; + + // Configuration options for payment routing and pathfinding. + optional types.RouteParametersConfig route_parameters = 3; } @@ -199,6 +202,9 @@ message Bolt12SendRequest { // If set, it will be seen by the recipient and reflected back in the invoice. optional string payer_note = 4; + + // Configuration options for payment routing and pathfinding. + optional types.RouteParametersConfig route_parameters = 5; } // The response `content` for the `Bolt12Send` API, when HttpStatusCode is OK (200). diff --git a/ldk-server-protos/src/proto/types.proto b/ldk-server-protos/src/proto/types.proto index 5f41441..7306169 100644 --- a/ldk-server-protos/src/proto/types.proto +++ b/ldk-server-protos/src/proto/types.proto @@ -683,3 +683,24 @@ message Bolt11InvoiceDescription { string hash = 2; } } + +// Configuration options for payment routing and pathfinding. +// See https://docs.rs/lightning/0.2.0/lightning/routing/router/struct.RouteParametersConfig.html for more details on each field. +message RouteParametersConfig { + // The maximum total fees, in millisatoshi, that may accrue during route finding. + // Defaults to 1% of the payment amount + 50 sats + optional uint64 max_total_routing_fee_msat = 1; + + // The maximum total CLTV delta we accept for the route. + // Defaults to 1008. + uint32 max_total_cltv_expiry_delta = 2; + + // The maximum number of paths that may be used by (MPP) payments. + // Defaults to 10. + uint32 max_path_count = 3; + + // Selects the maximum share of a channel's total capacity which will be + // sent over a channel, as a power of 1/2. + // Default value: 2 + uint32 max_channel_saturation_power_of_half = 4; +} diff --git a/ldk-server-protos/src/types.rs b/ldk-server-protos/src/types.rs index 2e5dc8c..3251bc4 100644 --- a/ldk-server-protos/src/types.rs +++ b/ldk-server-protos/src/types.rs @@ -779,6 +779,29 @@ pub mod bolt11_invoice_description { Hash(::prost::alloc::string::String), } } +/// Configuration options for payment routing and pathfinding. +/// See for more details on each field. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RouteParametersConfig { + /// The maximum total fees, in millisatoshi, that may accrue during route finding. + /// Defaults to 1% of the payment amount + 50 sats + #[prost(uint64, optional, tag = "1")] + pub max_total_routing_fee_msat: ::core::option::Option, + /// The maximum total CLTV delta we accept for the route. + /// Defaults to 1008. + #[prost(uint32, tag = "2")] + pub max_total_cltv_expiry_delta: u32, + /// The maximum number of paths that may be used by (MPP) payments. + /// Defaults to 10. + #[prost(uint32, tag = "3")] + pub max_path_count: u32, + /// Selects the maximum share of a channel's total capacity which will be + /// sent over a channel, as a power of 1/2. + /// Default value: 2 + #[prost(uint32, tag = "4")] + pub max_channel_saturation_power_of_half: u32, +} /// Represents the direction of a payment. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] diff --git a/ldk-server/src/api/bolt11_send.rs b/ldk-server/src/api/bolt11_send.rs index 6a13143..fd22b41 100644 --- a/ldk-server/src/api/bolt11_send.rs +++ b/ldk-server/src/api/bolt11_send.rs @@ -1,5 +1,7 @@ use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; +use ldk_node::lightning::routing::router::RouteParametersConfig; use ldk_node::lightning_invoice::Bolt11Invoice; use ldk_server_protos::api::{Bolt11SendRequest, Bolt11SendResponse}; use std::str::FromStr; @@ -12,10 +14,38 @@ pub(crate) fn handle_bolt11_send_request( let invoice = Bolt11Invoice::from_str(&request.invoice.as_str()) .map_err(|_| ldk_node::NodeError::InvalidInvoice)?; + let route_parameters = match request.route_parameters { + Some(params) => { + let max_path_count: u8 = params.max_path_count.try_into().map_err(|_| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid max_path_count, must be between 0 and {}", u8::MAX), + ) + })?; + let max_channel_saturation_power_of_half: u8 = + params.max_channel_saturation_power_of_half.try_into().map_err(|_| { + LdkServerError::new( + InvalidRequestError, + format!( + "Invalid max_channel_saturation_power_of_half, must be between 0 and {}", + u8::MAX + ), + ) + })?; + Some(RouteParametersConfig { + max_total_routing_fee_msat: params.max_total_routing_fee_msat, + max_total_cltv_expiry_delta: params.max_total_cltv_expiry_delta, + max_path_count, + max_channel_saturation_power_of_half, + }) + }, + None => None, + }; + let payment_id = match request.amount_msat { - None => context.node.bolt11_payment().send(&invoice, None), + None => context.node.bolt11_payment().send(&invoice, route_parameters), Some(amount_msat) => { - context.node.bolt11_payment().send_using_amount(&invoice, amount_msat, None) + context.node.bolt11_payment().send_using_amount(&invoice, amount_msat, route_parameters) }, }?; diff --git a/ldk-server/src/api/bolt12_send.rs b/ldk-server/src/api/bolt12_send.rs index bbf392a..439fe13 100644 --- a/ldk-server/src/api/bolt12_send.rs +++ b/ldk-server/src/api/bolt12_send.rs @@ -1,6 +1,8 @@ use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; use ldk_node::lightning::offers::offer::Offer; +use ldk_node::lightning::routing::router::RouteParametersConfig; use ldk_server_protos::api::{Bolt12SendRequest, Bolt12SendResponse}; use std::str::FromStr; @@ -12,16 +14,47 @@ pub(crate) fn handle_bolt12_send_request( let offer = Offer::from_str(&request.offer.as_str()).map_err(|_| ldk_node::NodeError::InvalidOffer)?; - let payment_id = match request.amount_msat { - None => { - context.node.bolt12_payment().send(&offer, request.quantity, request.payer_note, None) + let route_parameters = match request.route_parameters { + Some(params) => { + let max_path_count = params.max_path_count.try_into().map_err(|_| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid max_path_count, must be between 0 and {}", u8::MAX), + ) + })?; + let max_channel_saturation_power_of_half = + params.max_channel_saturation_power_of_half.try_into().map_err(|_| { + LdkServerError::new( + InvalidRequestError, + format!( + "Invalid max_channel_saturation_power_of_half, must be between 0 and {}", + u8::MAX + ), + ) + })?; + Some(RouteParametersConfig { + max_total_routing_fee_msat: params.max_total_routing_fee_msat, + max_total_cltv_expiry_delta: params.max_total_cltv_expiry_delta, + max_path_count, + max_channel_saturation_power_of_half, + }) }, + None => None, + }; + + let payment_id = match request.amount_msat { + None => context.node.bolt12_payment().send( + &offer, + request.quantity, + request.payer_note, + route_parameters, + ), Some(amount_msat) => context.node.bolt12_payment().send_using_amount( &offer, amount_msat, request.quantity, request.payer_note, - None, + route_parameters, ), }?;