From a2fdf9c48d1f1f7a74d9d613cd4646866d939c81 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 7 Apr 2026 15:35:09 -0500 Subject: [PATCH 1/2] Return error for invalid onchain send fee rate Previously, an invalid fee_rate_sat_per_vb (overflows) was silently collapsed into None via .and_then(), causing the node to substitute its default fee estimation without informing the user. Now the server returns an InvalidRequestError and the CLI rejects invalid fee rates before sending the request. Co-Authored-By: Claude Opus 4.6 (1M context) --- ldk-server-cli/src/main.rs | 6 ++++++ ldk-server/src/api/onchain_send.rs | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index c13ec9bc..779717b0 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -634,6 +634,12 @@ async fn main() { ); }, Commands::OnchainSend { address, amount, send_all, fee_rate_sat_per_vb } => { + if let Some(rate) = fee_rate_sat_per_vb { + // Mirrors FeeRate::from_sat_per_vb: sat/vb * 1000/4 = sat/kwu + if rate.checked_mul(1000 / 4).is_none() { + handle_error_msg(&format!("Invalid fee rate: {} sat/vB", rate)); + } + } let amount_sats = amount.map(|a| a.to_sat().unwrap_or_else(|e| handle_error_msg(&e))); handle_response_result::<_, OnchainSendResponse>( client diff --git a/ldk-server/src/api/onchain_send.rs b/ldk-server/src/api/onchain_send.rs index 6eb1d63a..98d560e4 100644 --- a/ldk-server/src/api/onchain_send.rs +++ b/ldk-server/src/api/onchain_send.rs @@ -29,7 +29,12 @@ pub(crate) fn handle_onchain_send_request( ) })?; - let fee_rate = request.fee_rate_sat_per_vb.and_then(FeeRate::from_sat_per_vb); + let fee_rate = match request.fee_rate_sat_per_vb { + Some(rate) => Some(FeeRate::from_sat_per_vb(rate).ok_or_else(|| { + LdkServerError::new(InvalidRequestError, format!("Invalid fee rate: {} sat/vB", rate)) + })?), + None => None, + }; let txid = match (request.amount_sats, request.send_all) { (Some(amount_sats), None) => { context.node.onchain_payment().send_to_address(&address, amount_sats, fee_rate)? From d86f651009cae225a7e447e90221b34e1bc521bf Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 7 Apr 2026 15:36:39 -0500 Subject: [PATCH 2/2] Error when quantity set without amount in bolt12_receive Previously, setting quantity without amount_msat silently produced a variable-amount offer with no quantity constraint. Now both the server and CLI reject this misconfiguration with a clear error message. Co-Authored-By: Claude Opus 4.6 (1M context) --- ldk-server-cli/src/main.rs | 5 +++++ ldk-server/src/api/bolt12_receive.rs | 30 +++++++++++++++++----------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index 779717b0..330305c9 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -780,6 +780,11 @@ async fn main() { ); }, Commands::Bolt12Receive { description, amount, expiry_secs, quantity } => { + if quantity.is_some() && amount.is_none() { + handle_error_msg( + "quantity can only be set for fixed-amount offers (amount must be provided)", + ); + } let amount_msat = amount.map(|a| a.to_msat()); handle_response_result::<_, Bolt12ReceiveResponse>( client diff --git a/ldk-server/src/api/bolt12_receive.rs b/ldk-server/src/api/bolt12_receive.rs index 42f4b489..307c29fe 100644 --- a/ldk-server/src/api/bolt12_receive.rs +++ b/ldk-server/src/api/bolt12_receive.rs @@ -11,23 +11,29 @@ use hex::DisplayHex; use ldk_server_protos::api::{Bolt12ReceiveRequest, Bolt12ReceiveResponse}; use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; pub(crate) fn handle_bolt12_receive_request( context: Context, request: Bolt12ReceiveRequest, ) -> Result { - let offer = match request.amount_msat { - Some(amount_msat) => context.node.bolt12_payment().receive( - amount_msat, - &request.description, - request.expiry_secs, - request.quantity, - )?, - None => context - .node - .bolt12_payment() - .receive_variable_amount(&request.description, request.expiry_secs)?, - }; + let offer = + match (request.amount_msat, request.quantity) { + (Some(amount_msat), quantity) => context.node.bolt12_payment().receive( + amount_msat, + &request.description, + request.expiry_secs, + quantity, + )?, + (None, Some(_)) => return Err(LdkServerError::new( + InvalidRequestError, + "quantity can only be set for fixed-amount offers (amount_msat must be provided)", + )), + (None, None) => context + .node + .bolt12_payment() + .receive_variable_amount(&request.description, request.expiry_secs)?, + }; let offer_id = offer.id().0.to_lower_hex_string(); let response = Bolt12ReceiveResponse { offer: offer.to_string(), offer_id };