splice: Add easy spliceout command
What changed, and why it matters
This commit adds a new user-facing command called 'spliceout' that makes it simpler to remove funds from a Lightning channel via splicing. It is a feature addition built on top of an existing splicing framework and does not appear to fix any security issue.
No security action required; review as a normal feature addition. If auditing, verify that the generated splice script is correctly sanitized/validated by the existing splice parser and that defaulting destination to 'wallet' behaves as documented.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces json_spliceout() in plugins/spender/splice.c, which parses channel, amount, optional destination (defaulting to ‘wallet’), and force_feerate parameters, then constructs a splice script string and reuses the existing splice command machinery (listpeerchannels callback, etc.). It also registers the new ‘spliceout’ RPC command and removes an @pytest.mark.xfail(strict=True) decorator from a related test, indicating the test now passes with the new functionality.
Changed components
plugins/spender/splice.ctests/test_splice.pyInspect captured patch +54 / −1
diff --git a/plugins/spender/splice.c b/plugins/spender/splice.c
index 403eb366..4c83f982 100644
--- a/plugins/spender/splice.c
+++ b/plugins/spender/splice.c
@@ -2246,6 +2246,56 @@ json_splicein(struct command *cmd, const char *buf, const jsmntok_t *params)
return send_outreq(req);
}
+static struct command_result *
+json_spliceout(struct command *cmd, const char *buf, const jsmntok_t *params)
+{
+ struct out_req *req;
+ const char *channel, *amount, *destination;
+ struct splice_cmd *splice_cmd;
+ bool *force_feerate;
+ char *script;
+
+ if (!param(cmd, buf, params,
+ p_req("channel", param_string, &channel),
+ p_req("amount", param_string, &amount),
+ p_opt("destination", param_string, &destination),
+ p_opt_def("force_feerate", param_bool, &force_feerate,
+ false),
+ NULL))
+ return command_param_failed();
+
+ if (!destination)
+ destination = "wallet";
+
+ script = tal_fmt(NULL,
+ "%s -> %s + fee; 100%% -> %s",
+ channel, amount, destination);
+
+ splice_cmd = tal(cmd, struct splice_cmd);
+
+ splice_cmd->cmd = cmd;
+ splice_cmd->script = tal_steal(splice_cmd, script);
+ splice_cmd->psbt = create_psbt(splice_cmd, 0, 0, 0);
+ splice_cmd->dryrun = false;
+ splice_cmd->wetrun = false;
+ splice_cmd->feerate_per_kw = 0;
+ splice_cmd->force_feerate = *force_feerate;
+ splice_cmd->wallet_inputs_to_signed = 0;
+ splice_cmd->fee_calculated = false;
+ splice_cmd->initial_funds = AMOUNT_SAT(0);
+ splice_cmd->emergency_sat = AMOUNT_SAT(0);
+ splice_cmd->debug_log = NULL;
+ splice_cmd->debug_counter = 0;
+ splice_cmd->needed_funds = AMOUNT_SAT(0);
+ memset(&splice_cmd->final_txid, 0, sizeof(splice_cmd->final_txid));
+
+ req = jsonrpc_request_start(cmd, "listpeerchannels",
+ listpeerchannels_get_result,
+ splice_error, splice_cmd);
+
+ return send_outreq(req);
+}
+
const struct plugin_command splice_commands[] = {
{
"dev-splice",
@@ -2255,5 +2305,9 @@ const struct plugin_command splice_commands[] = {
"splicein",
json_splicein
},
+ {
+ "spliceout",
+ json_spliceout
+ },
};
const size_t num_splice_commands = ARRAY_SIZE(splice_commands);
diff --git a/tests/test_splice.py b/tests/test_splice.py
index 29d67b92..02040807 100644
--- a/tests/test_splice.py
+++ b/tests/test_splice.py
@@ -621,7 +621,6 @@ def test_script_two_chan_splice_z(node_factory, bitcoind):
[500000 + int(100000 * 0.7), 500000 + int(100000 * 0.3)], [-0.7, -0.3])
-@pytest.mark.xfail(strict=True)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.