diff --git a/e2e-tests/tests/e2e.rs b/e2e-tests/tests/e2e.rs index 423838f1..5ebf134c 100644 --- a/e2e-tests/tests/e2e.rs +++ b/e2e-tests/tests/e2e.rs @@ -23,7 +23,8 @@ use ldk_node::lightning::offers::offer::Offer; use ldk_node::lightning_invoice::Bolt11Invoice; use ldk_server_client::client::EventStream; use ldk_server_client::ldk_server_grpc::api::{ - Bolt11ReceiveRequest, Bolt12ReceiveRequest, OnchainReceiveRequest, OpenChannelRequest, + Bolt11ReceiveRequest, Bolt12ReceiveRequest, GetBalancesRequest, OnchainReceiveRequest, + OpenChannelRequest, }; use ldk_server_client::ldk_server_grpc::events::event_envelope::Event; use ldk_server_client::ldk_server_grpc::events::{ @@ -360,6 +361,42 @@ async fn test_cli_onchain_send() { assert!(!output["txid"].as_str().unwrap().is_empty()); } +#[tokio::test] +async fn test_cli_onchain_send_all() { + let bitcoind = TestBitcoind::new(); + let server = LdkServerHandle::start(&bitcoind).await; + + let funding_address = + server.client().onchain_receive(OnchainReceiveRequest {}).await.unwrap().address; + bitcoind.fund_address(&funding_address, 1.0); + mine_and_sync(&bitcoind, &[&server], 6).await; + wait_for_onchain_balance(server.client(), Duration::from_secs(30)).await; + + let balances_before = server.client().get_balances(GetBalancesRequest {}).await.unwrap(); + + let address = bitcoind.bitcoind.client.new_address().unwrap().to_string(); + let output = run_cli(&server, &["onchain-send", &address, "--send-all", "true"]); + assert!(!output["txid"].as_str().unwrap().is_empty()); + + mine_and_sync(&bitcoind, &[&server], 6).await; + + let timeout = Duration::from_secs(30); + let start = std::time::Instant::now(); + let balances = loop { + let balances = server.client().get_balances(GetBalancesRequest {}).await.unwrap(); + if balances.total_onchain_balance_sats != balances_before.total_onchain_balance_sats { + break balances; + } + if start.elapsed() > timeout { + panic!("Timed out waiting for on-chain balance to change"); + } + tokio::time::sleep(Duration::from_millis(500)).await; + }; + + assert_eq!(balances.spendable_onchain_balance_sats, 0); + assert_eq!(balances.total_onchain_balance_sats, 0); +} + #[tokio::test] async fn test_cli_connect_peer() { let bitcoind = TestBitcoind::new(); diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index 918f57a3..f6f7c472 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -118,7 +118,7 @@ enum Commands { amount: Option, #[arg( long, - help = "Send full balance to the address. Warning: will not retain on-chain reserves for anchor channels" + help = "Send all available balance to the address while retaining on-chain reserves for anchor channels" )] send_all: Option, #[arg( diff --git a/ldk-server-grpc/src/api.rs b/ldk-server-grpc/src/api.rs index 26016bf4..179e3082 100644 --- a/ldk-server-grpc/src/api.rs +++ b/ldk-server-grpc/src/api.rs @@ -125,11 +125,9 @@ pub struct OnchainSendRequest { #[prost(uint64, optional, tag = "2")] pub amount_sats: ::core::option::Option, /// If set, the amount_sats field should be unset. - /// It indicates that node will send full balance to the specified address. + /// It indicates that the node will send all available balance to the specified address. /// - /// Please note that when send_all is used this operation will **not** retain any on-chain reserves, - /// which might be potentially dangerous if you have open Anchor channels for which you can't trust - /// the counterparty to spend the Anchor output after channel closure. + /// Any on-chain reserves needed for Anchor channels will be retained. /// See more: #[prost(bool, optional, tag = "3")] pub send_all: ::core::option::Option, diff --git a/ldk-server-grpc/src/proto/api.proto b/ldk-server-grpc/src/proto/api.proto index 244af5d9..64d70a91 100644 --- a/ldk-server-grpc/src/proto/api.proto +++ b/ldk-server-grpc/src/proto/api.proto @@ -103,11 +103,9 @@ message OnchainSendRequest { optional uint64 amount_sats = 2; // If set, the amount_sats field should be unset. - // It indicates that node will send full balance to the specified address. + // It indicates that the node will send all available balance to the specified address. // - // Please note that when send_all is used this operation will **not** retain any on-chain reserves, - // which might be potentially dangerous if you have open Anchor channels for which you can't trust - // the counterparty to spend the Anchor output after channel closure. + // Any on-chain reserves needed for Anchor channels will be retained. // See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.OnchainPayment.html#method.send_all_to_address optional bool send_all = 3; diff --git a/ldk-server-mcp/src/tools/schema.rs b/ldk-server-mcp/src/tools/schema.rs index b9c45144..84b592cd 100644 --- a/ldk-server-mcp/src/tools/schema.rs +++ b/ldk-server-mcp/src/tools/schema.rs @@ -168,7 +168,7 @@ pub fn onchain_send_schema() -> Value { }, "send_all": { "type": "boolean", - "description": "If true, send full balance (ignores amount_sats). Warning: will not retain on-chain reserves for anchor channels" + "description": "If true, send all available balance while retaining on-chain reserves for anchor channels (amount_sats must be unset)" }, "fee_rate_sat_per_vb": { "type": "integer", diff --git a/ldk-server/src/api/onchain_send.rs b/ldk-server/src/api/onchain_send.rs index ea616a17..4485824c 100644 --- a/ldk-server/src/api/onchain_send.rs +++ b/ldk-server/src/api/onchain_send.rs @@ -35,9 +35,8 @@ pub(crate) async fn handle_onchain_send_request( (Some(amount_sats), None) => { context.node.onchain_payment().send_to_address(&address, amount_sats, fee_rate)? }, - // Retain existing api behaviour to not retain reserves on `send_all_to_address`. (None, Some(true)) => { - context.node.onchain_payment().send_all_to_address(&address, false, fee_rate)? + context.node.onchain_payment().send_all_to_address(&address, true, fee_rate)? }, _ => { return Err(LdkServerError::new(