Skip to content

Commit 289f765

Browse files
committed
Add Node::splice_out method
Instead of closing and re-opening a channel when on-chain funds are needed, splicing allows removing funds (splice-out) while keeping the channel operational. This commit implements splice-out sending funds to a user-provided on-chain address.
1 parent 7104ed2 commit 289f765

File tree

2 files changed

+58
-7
lines changed

2 files changed

+58
-7
lines changed

src/lib.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
106106

107107
pub use balance::{BalanceDetails, LightningBalance, PendingSweepBalance};
108108
use bitcoin::secp256k1::PublicKey;
109-
use bitcoin::Amount;
109+
use bitcoin::{Address, Amount};
110110
#[cfg(feature = "uniffi")]
111111
pub use builder::ArcedNodeBuilder as Builder;
112112
pub use builder::BuildError;
@@ -1292,6 +1292,59 @@ impl Node {
12921292
}
12931293
}
12941294

1295+
/// Remove funds from an existing channel, sending them to an on-chain address.
1296+
///
1297+
/// This provides for decreasing a channel's outbound liquidity without re-balancing or closing
1298+
/// it. Once negotiation with the counterparty is complete, the channel remains operational
1299+
/// while waiting for a new funding transaction to confirm.
1300+
pub fn splice_out(
1301+
&self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, address: Address,
1302+
splice_amount_sats: u64,
1303+
) -> Result<(), Error> {
1304+
let open_channels =
1305+
self.channel_manager.list_channels_with_counterparty(&counterparty_node_id);
1306+
if let Some(channel_details) =
1307+
open_channels.iter().find(|c| c.user_channel_id == user_channel_id.0)
1308+
{
1309+
if splice_amount_sats > channel_details.outbound_capacity_msat {
1310+
return Err(Error::ChannelSplicingFailed);
1311+
}
1312+
1313+
self.wallet.parse_and_validate_address(&address)?;
1314+
1315+
let contribution = SpliceContribution::SpliceOut {
1316+
outputs: vec![bitcoin::TxOut {
1317+
value: Amount::from_sat(splice_amount_sats),
1318+
script_pubkey: address.script_pubkey(),
1319+
}],
1320+
};
1321+
1322+
let fee_rate = self.wallet.estimate_channel_funding_fee_rate();
1323+
let funding_feerate_per_kw = fee_rate.to_sat_per_kwu().try_into().unwrap_or(u32::MAX);
1324+
1325+
self.channel_manager
1326+
.splice_channel(
1327+
&channel_details.channel_id,
1328+
&counterparty_node_id,
1329+
contribution,
1330+
funding_feerate_per_kw,
1331+
None,
1332+
)
1333+
.map_err(|e| {
1334+
log_error!(self.logger, "Failed to splice channel: {:?}", e);
1335+
Error::ChannelSplicingFailed
1336+
})
1337+
} else {
1338+
log_error!(
1339+
self.logger,
1340+
"Channel not found for user_channel_id: {:?} and counterparty: {}",
1341+
user_channel_id,
1342+
counterparty_node_id
1343+
);
1344+
Err(Error::ChannelSplicingFailed)
1345+
}
1346+
}
1347+
12951348
/// Manually sync the LDK and BDK wallets with the current chain state and update the fee rate
12961349
/// cache.
12971350
///

src/wallet/mod.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use bitcoin::secp256k1::ecdh::SharedSecret;
2525
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
2626
use bitcoin::secp256k1::{All, PublicKey, Scalar, Secp256k1, SecretKey};
2727
use bitcoin::{
28-
Address, Amount, FeeRate, Network, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, Weight,
28+
Address, Amount, FeeRate, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, Weight,
2929
WitnessProgram, WitnessVersion,
3030
};
3131
use lightning::chain::chaininterface::BroadcasterInterface;
@@ -338,12 +338,10 @@ impl Wallet {
338338
self.get_balances(total_anchor_channels_reserve_sats).map(|(_, s)| s)
339339
}
340340

341-
fn parse_and_validate_address(
342-
&self, network: Network, address: &Address,
343-
) -> Result<Address, Error> {
341+
pub(crate) fn parse_and_validate_address(&self, address: &Address) -> Result<Address, Error> {
344342
Address::<NetworkUnchecked>::from_str(address.to_string().as_str())
345343
.map_err(|_| Error::InvalidAddress)?
346-
.require_network(network)
344+
.require_network(self.config.network)
347345
.map_err(|_| Error::InvalidAddress)
348346
}
349347

@@ -352,7 +350,7 @@ impl Wallet {
352350
&self, address: &bitcoin::Address, send_amount: OnchainSendAmount,
353351
fee_rate: Option<FeeRate>,
354352
) -> Result<Txid, Error> {
355-
self.parse_and_validate_address(self.config.network, &address)?;
353+
self.parse_and_validate_address(&address)?;
356354

357355
// Use the set fee_rate or default to fee estimation.
358356
let confirmation_target = ConfirmationTarget::OnchainPayment;

0 commit comments

Comments
 (0)