Skip to content

Commit 7f3276a

Browse files
authored
Merge pull request #259 from tankyleo/2026-08-with-all
Expose with-all channel funding variants
2 parents 1af5168 + e3bc325 commit 7f3276a

12 files changed

Lines changed: 388 additions & 126 deletions

File tree

e2e-tests/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ use hex_conservative::DisplayHex;
1818
use ldk_server_client::client::LdkServerClient;
1919
use ldk_server_client::ldk_server_grpc::api::{GetNodeInfoRequest, GetNodeInfoResponse};
2020
use ldk_server_grpc::api::{
21-
GetBalancesRequest, ListChannelsRequest, OnchainReceiveRequest, OpenChannelRequest,
21+
open_channel_request, GetBalancesRequest, ListChannelsRequest, OnchainReceiveRequest,
22+
OpenChannelRequest,
2223
};
2324
use serde_json::Value;
2425

@@ -737,7 +738,9 @@ pub async fn setup_funded_channel(
737738
.open_channel(OpenChannelRequest {
738739
node_pubkey: server_b.node_id().to_string(),
739740
address: format!("127.0.0.1:{}", server_b.p2p_port),
740-
channel_amount_sats,
741+
amount: Some(open_channel_request::Amount::ChannelAmountSats(
742+
channel_amount_sats,
743+
)),
741744
push_to_counterparty_msat: None,
742745
channel_config: None,
743746
announce_channel: true,

e2e-tests/tests/e2e.rs

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ use ldk_node::lightning::offers::offer::Offer;
2323
use ldk_node::lightning_invoice::Bolt11Invoice;
2424
use ldk_server_client::client::EventStream;
2525
use ldk_server_client::ldk_server_grpc::api::{
26-
Bolt11ReceiveRequest, Bolt12ReceiveRequest, GetBalancesRequest, OnchainReceiveRequest,
27-
OpenChannelRequest,
26+
open_channel_request, Bolt11ReceiveRequest, Bolt12ReceiveRequest, GetBalancesRequest,
27+
OnchainReceiveRequest, OpenChannelRequest,
2828
};
2929
use ldk_server_client::ldk_server_grpc::events::event_envelope::Event;
3030
use ldk_server_client::ldk_server_grpc::events::{
@@ -376,7 +376,7 @@ async fn test_cli_onchain_send_all() {
376376
let balances_before = server.client().get_balances(GetBalancesRequest {}).await.unwrap();
377377

378378
let address = bitcoind.bitcoind.client.new_address().unwrap().to_string();
379-
let output = run_cli(&server, &["onchain-send", &address, "--send-all", "true"]);
379+
let output = run_cli(&server, &["onchain-send", &address, "all"]);
380380
assert!(!output["txid"].as_str().unwrap().is_empty());
381381

382382
mine_and_sync(&bitcoind, &[&server], 6).await;
@@ -435,8 +435,7 @@ async fn test_cli_list_peers() {
435435

436436
// === CLI tests: Group 4 — Two-node with channel ===
437437

438-
#[tokio::test]
439-
async fn test_cli_open_channel() {
438+
async fn open_channel_via_cli(channel_amount: &str) {
440439
let bitcoind = TestBitcoind::new();
441440
let server_a = LdkServerHandle::start(&bitcoind).await;
442441
let server_b = LdkServerHandle::start(&bitcoind).await;
@@ -454,11 +453,27 @@ async fn test_cli_open_channel() {
454453
let addr = format!("127.0.0.1:{}", server_b.p2p_port);
455454
let output = run_cli(
456455
&server_a,
457-
&["open-channel", server_b.node_id(), &addr, "100000sat", "--announce-channel"],
456+
&[
457+
"open-channel",
458+
server_b.node_id(),
459+
&addr,
460+
channel_amount,
461+
"--announce-channel",
462+
],
458463
);
459464
assert!(!output["user_channel_id"].as_str().unwrap().is_empty());
460465
}
461466

467+
#[tokio::test]
468+
async fn test_cli_open_channel() {
469+
open_channel_via_cli("100000sat").await;
470+
}
471+
472+
#[tokio::test]
473+
async fn test_cli_open_channel_with_all() {
474+
open_channel_via_cli("all").await;
475+
}
476+
462477
#[tokio::test]
463478
async fn test_subscribe_events_channel_state_lifecycle_pending_ready_closed() {
464479
let bitcoind = TestBitcoind::new();
@@ -481,7 +496,9 @@ async fn test_subscribe_events_channel_state_lifecycle_pending_ready_closed() {
481496
.open_channel(OpenChannelRequest {
482497
node_pubkey: server_b.node_id().to_string(),
483498
address: format!("127.0.0.1:{}", server_b.p2p_port),
484-
channel_amount_sats: 100_000,
499+
amount: Some(open_channel_request::Amount::ChannelAmountSats(
500+
100_000,
501+
)),
485502
push_to_counterparty_msat: None,
486503
channel_config: None,
487504
announce_channel: true,
@@ -645,7 +662,9 @@ async fn test_subscribe_events_channel_state_lifecycle_pending_ready_force_close
645662
.open_channel(OpenChannelRequest {
646663
node_pubkey: server_b.node_id().to_string(),
647664
address: format!("127.0.0.1:{}", server_b.p2p_port),
648-
channel_amount_sats: 100_000,
665+
amount: Some(open_channel_request::Amount::ChannelAmountSats(
666+
100_000,
667+
)),
649668
push_to_counterparty_msat: None,
650669
channel_config: None,
651670
announce_channel: true,
@@ -1119,18 +1138,29 @@ async fn test_cli_force_close_channel() {
11191138
assert!(channels_output["channels"].as_array().unwrap().is_empty());
11201139
}
11211140

1122-
#[tokio::test]
1123-
async fn test_cli_splice_in() {
1141+
async fn splice_in_via_cli(splice_amount: &str) {
11241142
let bitcoind = TestBitcoind::new();
11251143
let server_a = LdkServerHandle::start(&bitcoind).await;
11261144
let server_b = LdkServerHandle::start(&bitcoind).await;
11271145
let user_channel_id = setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await;
11281146

1129-
let output =
1130-
run_cli(&server_a, &["splice-in", &user_channel_id, server_b.node_id(), "50000sat"]);
1147+
let output = run_cli(
1148+
&server_a,
1149+
&["splice-in", &user_channel_id, server_b.node_id(), splice_amount],
1150+
);
11311151
assert!(output.is_object());
11321152
}
11331153

1154+
#[tokio::test]
1155+
async fn test_cli_splice_in() {
1156+
splice_in_via_cli("50000sat").await;
1157+
}
1158+
1159+
#[tokio::test]
1160+
async fn test_cli_splice_in_with_all() {
1161+
splice_in_via_cli("all").await;
1162+
}
1163+
11341164
#[tokio::test]
11351165
async fn test_cli_splice_out() {
11361166
let bitcoind = TestBitcoind::new();

ldk-server-cli/src/main.rs

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use ldk_server_client::error::LdkServerErrorCode::{
2323
AuthError, InternalError, InternalServerError, InvalidRequestError, LightningError,
2424
};
2525
use ldk_server_client::ldk_server_grpc::api::{
26+
onchain_send_request, open_channel_request, splice_in_request, AllFunds,
2627
Bolt11ClaimForHashRequest, Bolt11ClaimForHashResponse, Bolt11FailForHashRequest,
2728
Bolt11FailForHashResponse, Bolt11ReceiveForHashRequest, Bolt11ReceiveForHashResponse,
2829
Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11ReceiveVariableAmountViaJitChannelRequest,
@@ -56,8 +57,8 @@ use ldk_server_client::{
5657
use serde::Serialize;
5758
use serde_json::{json, Value};
5859
use types::{
59-
Amount, CliListForwardedPaymentsResponse, CliListPaymentsResponse, CliPaginatedResponse,
60-
Preimage,
60+
Amount, AmountOrAll, CliListForwardedPaymentsResponse, CliListPaymentsResponse,
61+
CliPaginatedResponse, Preimage,
6162
};
6263

6364
mod types;
@@ -115,14 +116,9 @@ enum Commands {
115116
#[arg(help = "The address to send coins to")]
116117
address: String,
117118
#[arg(
118-
help = "The amount to send, e.g. 50sat or 50000msat, must be a whole sat amount, cannot send msats on-chain. Will respect any on-chain reserve needed for anchor channels"
119+
help = "The amount to send, e.g. 50sat or 50000msat, or 'all' to use all available on-chain funds. Exact amounts must be a whole sat amount. Will respect any on-chain reserve needed for anchor channels"
119120
)]
120-
amount: Option<Amount>,
121-
#[arg(
122-
long,
123-
help = "Send all available balance to the address while retaining on-chain reserves for anchor channels"
124-
)]
125-
send_all: Option<bool>,
121+
amount: AmountOrAll,
126122
#[arg(
127123
long,
128124
help = "Fee rate in satoshis per virtual byte. If not set, a reasonable estimate will be used"
@@ -419,9 +415,9 @@ enum Commands {
419415
)]
420416
address: String,
421417
#[arg(
422-
help = "The amount to commit to the channel, e.g. 100sat or 100000msat, must be a whole sat amount, cannot send msats on-chain."
418+
help = "The amount to commit to the channel, e.g. 100sat or 100000msat, or 'all' to use all available on-chain funds. Exact amounts must be a whole sat amount."
423419
)]
424-
channel_amount: Amount,
420+
channel_amount: AmountOrAll,
425421
#[arg(long, help = "Amount to push to the remote side, e.g. 50sat or 50000msat")]
426422
push_to_counterparty: Option<Amount>,
427423
#[arg(long, help = "Whether the channel should be public")]
@@ -457,9 +453,9 @@ enum Commands {
457453
#[arg(help = "The hex-encoded public key of the channel's counterparty node")]
458454
counterparty_node_id: String,
459455
#[arg(
460-
help = "The amount to splice into the channel, e.g. 50sat or 50000msat, must be a whole sat amount, cannot send msats on-chain."
456+
help = "The amount to splice into the channel, e.g. 50sat or 50000msat, or 'all' to use all available on-chain funds. Exact amounts must be a whole sat amount."
461457
)]
462-
splice_amount: Amount,
458+
splice_amount: AmountOrAll,
463459
},
464460
#[command(about = "Decrease the channel balance by the given amount")]
465461
SpliceOut {
@@ -660,15 +656,17 @@ async fn main() {
660656
client.onchain_receive(OnchainReceiveRequest {}).await,
661657
);
662658
},
663-
Commands::OnchainSend { address, amount, send_all, fee_rate_sat_per_vb } => {
664-
let amount_sats = amount.map(|a| a.to_sat().unwrap_or_else(|e| handle_error_msg(e)));
659+
Commands::OnchainSend { address, amount, fee_rate_sat_per_vb } => {
660+
let amount = match amount.to_sat().unwrap_or_else(|e| handle_error_msg(e)) {
661+
Some(amount_sats) => onchain_send_request::Amount::AmountSats(amount_sats),
662+
None => onchain_send_request::Amount::AllFunds(AllFunds {}),
663+
};
665664
handle_response_result::<_, OnchainSendResponse>(
666665
client
667666
.onchain_send(OnchainSendRequest {
668667
address,
669-
amount_sats,
670-
send_all,
671668
fee_rate_sat_per_vb,
669+
amount: Some(amount),
672670
})
673671
.await,
674672
);
@@ -983,8 +981,10 @@ async fn main() {
983981
forwarding_fee_base_msat,
984982
cltv_expiry_delta,
985983
} => {
986-
let channel_amount_sats =
987-
channel_amount.to_sat().unwrap_or_else(|e| handle_error_msg(e));
984+
let amount = match channel_amount.to_sat().unwrap_or_else(|e| handle_error_msg(e)) {
985+
Some(amount_sats) => open_channel_request::Amount::ChannelAmountSats(amount_sats),
986+
None => open_channel_request::Amount::AllFunds(AllFunds {}),
987+
};
988988
let push_to_counterparty_msat = push_to_counterparty.map(|a| a.to_msat());
989989
let channel_config = build_open_channel_config(
990990
forwarding_fee_proportional_millionths,
@@ -1003,7 +1003,7 @@ async fn main() {
10031003
.open_channel(OpenChannelRequest {
10041004
node_pubkey,
10051005
address,
1006-
channel_amount_sats,
1006+
amount: Some(amount),
10071007
push_to_counterparty_msat,
10081008
channel_config,
10091009
announce_channel,
@@ -1013,13 +1013,16 @@ async fn main() {
10131013
);
10141014
},
10151015
Commands::SpliceIn { user_channel_id, counterparty_node_id, splice_amount } => {
1016-
let splice_amount_sats = splice_amount.to_sat().unwrap_or_else(|e| handle_error_msg(e));
1016+
let amount = match splice_amount.to_sat().unwrap_or_else(|e| handle_error_msg(e)) {
1017+
Some(amount_sats) => splice_in_request::Amount::SpliceAmountSats(amount_sats),
1018+
None => splice_in_request::Amount::AllFunds(AllFunds {}),
1019+
};
10171020
handle_response_result::<_, SpliceInResponse>(
10181021
client
10191022
.splice_in(SpliceInRequest {
10201023
user_channel_id,
10211024
counterparty_node_id,
1022-
splice_amount_sats,
1025+
amount: Some(amount),
10231026
})
10241027
.await,
10251028
);

ldk-server-cli/src/types.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,35 @@ impl FromStr for Amount {
120120
}
121121
}
122122

123+
/// An exact on-chain amount or all available on-chain funds.
124+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125+
pub enum AmountOrAll {
126+
Exact(Amount),
127+
All,
128+
}
129+
130+
impl AmountOrAll {
131+
/// Returns the exact amount in satoshis, or `None` when all funds should be used.
132+
pub fn to_sat(self) -> Result<Option<u64>, String> {
133+
match self {
134+
Self::Exact(amount) => amount.to_sat().map(Some),
135+
Self::All => Ok(None),
136+
}
137+
}
138+
}
139+
140+
impl FromStr for AmountOrAll {
141+
type Err = String;
142+
143+
fn from_str(s: &str) -> Result<Self, Self::Err> {
144+
if s.trim() == "all" {
145+
Ok(Self::All)
146+
} else {
147+
Amount::from_str(s).map(Self::Exact)
148+
}
149+
}
150+
}
151+
123152
/// A validated 32-byte payment preimage, parsed from a 64-character hex string.
124153
#[derive(Debug, Clone)]
125154
pub struct Preimage(pub [u8; 32]);
@@ -207,6 +236,14 @@ mod tests {
207236
assert!(Amount::from_str(&big).is_err());
208237
}
209238

239+
#[test]
240+
fn amount_or_all_parses_exact_amount_or_all() {
241+
assert_eq!(AmountOrAll::from_str("all").unwrap(), AmountOrAll::All);
242+
assert_eq!(AmountOrAll::from_str(" all ").unwrap(), AmountOrAll::All);
243+
assert_eq!(AmountOrAll::from_str("100sat").unwrap().to_sat().unwrap(), Some(100));
244+
assert_eq!(AmountOrAll::All.to_sat().unwrap(), None);
245+
}
246+
210247
#[test]
211248
fn preimage_parsing_and_roundtrip() {
212249
// valid 64-char hex string

0 commit comments

Comments
 (0)