From 40f5ddcd63fb143a89f7a835ba2761f30046fc23 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 9 Dec 2025 18:16:40 -0600 Subject: [PATCH 1/2] Hook up ChannelConfig in open channel endpoint --- ldk-server/src/api/mod.rs | 49 +++++++++++++++++++++ ldk-server/src/api/open_channel.rs | 12 +++-- ldk-server/src/api/update_channel_config.rs | 49 +-------------------- 3 files changed, 60 insertions(+), 50 deletions(-) diff --git a/ldk-server/src/api/mod.rs b/ldk-server/src/api/mod.rs index c1b0fa2..834c7e4 100644 --- a/ldk-server/src/api/mod.rs +++ b/ldk-server/src/api/mod.rs @@ -1,3 +1,8 @@ +use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::InvalidRequestError; +use ldk_node::config::{ChannelConfig, MaxDustHTLCExposure}; +use ldk_server_protos::types::channel_config::MaxDustHtlcExposure; + pub(crate) mod bolt11_receive; pub(crate) mod bolt11_send; pub(crate) mod bolt12_receive; @@ -14,3 +19,47 @@ pub(crate) mod onchain_receive; pub(crate) mod onchain_send; pub(crate) mod open_channel; pub(crate) mod update_channel_config; + +pub(crate) fn build_channel_config_from_proto( + default_config: ChannelConfig, proto_channel_config: ldk_server_protos::types::ChannelConfig, +) -> Result { + let max_dust_htlc_exposure = proto_channel_config + .max_dust_htlc_exposure + .map(|max_dust_htlc_exposure| match max_dust_htlc_exposure { + MaxDustHtlcExposure::FixedLimitMsat(limit_msat) => { + MaxDustHTLCExposure::FixedLimit { limit_msat } + }, + MaxDustHtlcExposure::FeeRateMultiplier(multiplier) => { + MaxDustHTLCExposure::FeeRateMultiplier { multiplier } + }, + }) + .unwrap_or(default_config.max_dust_htlc_exposure); + + let cltv_expiry_delta = match proto_channel_config.cltv_expiry_delta { + Some(c) => Some(u16::try_from(c).map_err(|_| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid cltv_expiry_delta, must be between 0 and {}", u16::MAX), + ) + })?), + None => None, + } + .unwrap_or(default_config.cltv_expiry_delta); + + Ok(ChannelConfig { + forwarding_fee_proportional_millionths: proto_channel_config + .forwarding_fee_proportional_millionths + .unwrap_or(default_config.forwarding_fee_proportional_millionths), + forwarding_fee_base_msat: proto_channel_config + .forwarding_fee_base_msat + .unwrap_or(default_config.forwarding_fee_base_msat), + cltv_expiry_delta, + max_dust_htlc_exposure, + force_close_avoidance_max_fee_satoshis: proto_channel_config + .force_close_avoidance_max_fee_satoshis + .unwrap_or(default_config.force_close_avoidance_max_fee_satoshis), + accept_underpaying_htlcs: proto_channel_config + .accept_underpaying_htlcs + .unwrap_or(default_config.accept_underpaying_htlcs), + }) +} diff --git a/ldk-server/src/api/open_channel.rs b/ldk-server/src/api/open_channel.rs index 96fdbf2..c26dab0 100644 --- a/ldk-server/src/api/open_channel.rs +++ b/ldk-server/src/api/open_channel.rs @@ -1,6 +1,8 @@ +use crate::api::build_channel_config_from_proto; use crate::api::error::LdkServerError; use crate::service::Context; use ldk_node::bitcoin::secp256k1::PublicKey; +use ldk_node::config::ChannelConfig; use ldk_node::lightning::ln::msgs::SocketAddress; use ldk_server_protos::api::{OpenChannelRequest, OpenChannelResponse}; use std::str::FromStr; @@ -15,14 +17,18 @@ pub(crate) fn handle_open_channel( let address = SocketAddress::from_str(&request.address) .map_err(|_| ldk_node::NodeError::InvalidSocketAddress)?; + let channel_config = request + .channel_config + .map(|proto_config| build_channel_config_from_proto(ChannelConfig::default(), proto_config)) + .transpose()?; + let user_channel_id = if request.announce_channel { context.node.open_announced_channel( node_id, address, request.channel_amount_sats, request.push_to_counterparty_msat, - // TODO: Allow setting ChannelConfig in open-channel. - None, + channel_config, )? } else { context.node.open_channel( @@ -30,7 +36,7 @@ pub(crate) fn handle_open_channel( address, request.channel_amount_sats, request.push_to_counterparty_msat, - None, + channel_config, )? }; diff --git a/ldk-server/src/api/update_channel_config.rs b/ldk-server/src/api/update_channel_config.rs index 2a01a54..e5905c2 100644 --- a/ldk-server/src/api/update_channel_config.rs +++ b/ldk-server/src/api/update_channel_config.rs @@ -1,11 +1,10 @@ +use crate::api::build_channel_config_from_proto; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::{InvalidRequestError, LightningError}; use crate::service::Context; use ldk_node::bitcoin::secp256k1::PublicKey; -use ldk_node::config::{ChannelConfig, MaxDustHTLCExposure}; use ldk_node::UserChannelId; use ldk_server_protos::api::{UpdateChannelConfigRequest, UpdateChannelConfigResponse}; -use ldk_server_protos::types::channel_config::MaxDustHtlcExposure; use std::str::FromStr; pub(crate) const UPDATE_CHANNEL_CONFIG_PATH: &str = "UpdateChannelConfig"; @@ -29,7 +28,7 @@ pub(crate) fn handle_update_channel_config_request( })? .config; - let updated_channel_config = build_updated_channel_config( + let updated_channel_config = build_channel_config_from_proto( current_config, request.channel_config.ok_or_else(|| { LdkServerError::new(InvalidRequestError, "Channel config must be provided.") @@ -56,47 +55,3 @@ pub(crate) fn handle_update_channel_config_request( Ok(UpdateChannelConfigResponse {}) } - -fn build_updated_channel_config( - current_config: ChannelConfig, proto_channel_config: ldk_server_protos::types::ChannelConfig, -) -> Result { - let max_dust_htlc_exposure = proto_channel_config - .max_dust_htlc_exposure - .map(|max_dust_htlc_exposure| match max_dust_htlc_exposure { - MaxDustHtlcExposure::FixedLimitMsat(limit_msat) => { - MaxDustHTLCExposure::FixedLimit { limit_msat } - }, - MaxDustHtlcExposure::FeeRateMultiplier(multiplier) => { - MaxDustHTLCExposure::FeeRateMultiplier { multiplier } - }, - }) - .unwrap_or(current_config.max_dust_htlc_exposure); - - let cltv_expiry_delta = match proto_channel_config.cltv_expiry_delta { - Some(c) => Some(u16::try_from(c).map_err(|_| { - LdkServerError::new( - InvalidRequestError, - format!("Invalid cltv_expiry_delta, must be between 0 and {}", u16::MAX), - ) - })?), - None => None, - } - .unwrap_or_else(|| current_config.cltv_expiry_delta); - - Ok(ChannelConfig { - forwarding_fee_proportional_millionths: proto_channel_config - .forwarding_fee_proportional_millionths - .unwrap_or(current_config.forwarding_fee_proportional_millionths), - forwarding_fee_base_msat: proto_channel_config - .forwarding_fee_base_msat - .unwrap_or(current_config.forwarding_fee_base_msat), - cltv_expiry_delta, - max_dust_htlc_exposure, - force_close_avoidance_max_fee_satoshis: proto_channel_config - .force_close_avoidance_max_fee_satoshis - .unwrap_or(current_config.force_close_avoidance_max_fee_satoshis), - accept_underpaying_htlcs: proto_channel_config - .accept_underpaying_htlcs - .unwrap_or(current_config.accept_underpaying_htlcs), - }) -} From 22cae11b83746a2b7c8e27ccf5aba14652888997 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 9 Dec 2025 18:27:45 -0600 Subject: [PATCH 2/2] Add ChannelConfig options to open channel cli command These were available on the server side but not in the cli. Not all options are given as they aren't likely to be used and more likely to be confusing to users. --- ldk-server-cli/src/main.rs | 52 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index a45ec6e..d1de29c 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -11,7 +11,8 @@ use ldk_server_client::ldk_server_protos::api::{ OpenChannelRequest, }; use ldk_server_client::ldk_server_protos::types::{ - bolt11_invoice_description, Bolt11InvoiceDescription, PageToken, Payment, + bolt11_invoice_description, channel_config, Bolt11InvoiceDescription, ChannelConfig, PageToken, + Payment, }; use std::fmt::Debug; @@ -101,6 +102,22 @@ enum Commands { push_to_counterparty_msat: Option, #[arg(long)] announce_channel: bool, + // Channel config options + #[arg( + long, + help = "Amount (in millionths of a satoshi) charged per satoshi for payments forwarded outbound over the channel. This can be updated by using update-channel-config." + )] + forwarding_fee_proportional_millionths: Option, + #[arg( + long, + help = "Amount (in milli-satoshi) charged for payments forwarded outbound over the channel, in excess of forwarding_fee_proportional_millionths. This can be updated by using update-channel-config." + )] + forwarding_fee_base_msat: Option, + #[arg( + long, + help = "The difference in the CLTV value between incoming HTLCs and an outbound HTLC forwarded over the channel. This can be updated by using update-channel-config." + )] + cltv_expiry_delta: Option, }, ListChannels, ListPayments { @@ -213,7 +230,16 @@ async fn main() { channel_amount_sats, push_to_counterparty_msat, announce_channel, + forwarding_fee_proportional_millionths, + forwarding_fee_base_msat, + cltv_expiry_delta, } => { + let channel_config = build_open_channel_config( + forwarding_fee_proportional_millionths, + forwarding_fee_base_msat, + cltv_expiry_delta, + ); + handle_response_result( client .open_channel(OpenChannelRequest { @@ -221,7 +247,7 @@ async fn main() { address, channel_amount_sats, push_to_counterparty_msat, - channel_config: None, + channel_config, announce_channel, }) .await, @@ -236,6 +262,28 @@ async fn main() { } } +fn build_open_channel_config( + forwarding_fee_proportional_millionths: Option, forwarding_fee_base_msat: Option, + cltv_expiry_delta: Option, +) -> Option { + // Only create a config if at least one field is set + if forwarding_fee_proportional_millionths.is_none() + && forwarding_fee_base_msat.is_none() + && cltv_expiry_delta.is_none() + { + return None; + } + + Some(ChannelConfig { + forwarding_fee_proportional_millionths, + forwarding_fee_base_msat, + cltv_expiry_delta, + force_close_avoidance_max_fee_satoshis: None, + accept_underpaying_htlcs: None, + max_dust_htlc_exposure: None, + }) +} + async fn list_n_payments( client: LdkServerClient, number_of_payments: Option, ) -> Result, LdkServerError> {