From 71853434417f9635bec695083c6abb26d385185c Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 11 Feb 2026 13:27:05 +0100 Subject: [PATCH 1/7] gl-plugin: Use call_typed for regular invoice creation Replace call_raw with call_typed when creating a regular invoice in the LSP invoice handler. This provides better type safety by using the generated cln_rpc::model::requests::InvoiceRequest and cln_rpc::model::responses::InvoiceResponse types. Benefits: - Compile-time type checking for request/response fields - No manual hex decoding needed for payment_hash/payment_secret - Access to created_index field from the response - Cleaner code without custom request/response structs --- libs/gl-plugin/src/node/mod.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/libs/gl-plugin/src/node/mod.rs b/libs/gl-plugin/src/node/mod.rs index c865fb70c..f3bbd3cfc 100644 --- a/libs/gl-plugin/src/node/mod.rs +++ b/libs/gl-plugin/src/node/mod.rs @@ -25,6 +25,7 @@ use tokio_stream::wrappers::ReceiverStream; use tonic::{transport::ServerTlsConfig, Code, Request, Response, Status}; mod wrapper; use gl_client::bitcoin; +use std::borrow::Borrow; use std::str::FromStr; pub use wrapper::WrappedNodeServer; @@ -218,7 +219,7 @@ impl Node for PluginNodeServer { ); // Create a regular invoice without JIT channel negotiation - let invreq = crate::requests::Invoice { + let invreq = cln_rpc::model::requests::InvoiceRequest { amount_msat: cln_rpc::primitives::AmountOrAny::Amount( cln_rpc::primitives::Amount::from_msat(req.amount_msat), ), @@ -230,24 +231,19 @@ impl Node for PluginNodeServer { cltv: Some(144), deschashonly: None, exposeprivatechannels: None, - dev_routes: None, }; - let res: crate::responses::Invoice = rpc - .call_raw("invoice", &invreq) + let res = rpc + .call_typed(&invreq) .await .map_err(|e| Status::new(Code::Internal, e.to_string()))?; return Ok(Response::new(pb::LspInvoiceResponse { bolt11: res.bolt11, - created_index: 0, // Not available in our Invoice response - expires_at: res.expiry_time, - payment_hash: hex::decode(&res.payment_hash) - .map_err(|e| Status::new(Code::Internal, format!("Invalid payment_hash: {}", e)))?, - payment_secret: res - .payment_secret - .map(|s| hex::decode(&s).unwrap_or_default()) - .unwrap_or_default(), + created_index: res.created_index.unwrap_or(0) as u32, + expires_at: res.expires_at as u32, + payment_hash: >::borrow(&res.payment_hash).to_vec(), + payment_secret: res.payment_secret.to_vec(), })); } From 3c120fe643253dd8d1e02985279f3c0d37a463c8 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Fri, 13 Feb 2026 12:45:00 +0100 Subject: [PATCH 2/7] glcli: add newaddr command Signed-off-by: Lagrang3 --- libs/gl-cli/src/node.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/libs/gl-cli/src/node.rs b/libs/gl-cli/src/node.rs index 219a017a0..e045feb46 100644 --- a/libs/gl-cli/src/node.rs +++ b/libs/gl-cli/src/node.rs @@ -98,6 +98,8 @@ pub enum Command { )] status: Option, }, + /// Generates a new bitcoin address to receive funds + Newaddr, /// Stop the node Stop, } @@ -194,6 +196,7 @@ pub async fn command_handler>(cmd: Command, config: Config

) -> listpays_handler(config, bolt11, payment_hash, status).await } + Command::Newaddr => newaddr_handler(config).await, Command::Stop => stop(config).await, } } @@ -227,6 +230,25 @@ async fn init_handler>(config: Config

, mnemonic: Option>(config: Config

) -> Result { + let creds_path = config.data_dir.as_ref().join(CREDENTIALS_FILE_NAME); + let creds = match util::read_credentials(&creds_path) { + Some(c) => c, + None => { + return Err(Error::CredentialsNotFoundError(format!( + "could not read from {}", + creds_path.display() + ))) + } + }; + + let scheduler = gl_client::scheduler::Scheduler::new(config.network, creds) + .await + .map_err(Error::custom)?; + + scheduler.node().await.map_err(Error::custom) +} + async fn log>(config: Config

) -> Result<()> { let creds_path = config.data_dir.as_ref().join(CREDENTIALS_FILE_NAME); let creds = match util::read_credentials(&creds_path) { @@ -271,6 +293,17 @@ async fn log>(config: Config

) -> Result<()> { Ok(()) } +async fn newaddr_handler>(config: Config

) -> Result<()> { + let mut node: gl_client::node::ClnClient = get_node(config).await?; + let res = node + .new_addr(cln::NewaddrRequest { addresstype: None }) + .await + .map_err(|e| Error::custom(e.message()))? + .into_inner(); + println!("{:?}", res); + Ok(()) +} + async fn stop>(config: Config

) -> Result<()> { let creds_path = config.data_dir.as_ref().join(CREDENTIALS_FILE_NAME); let creds = match util::read_credentials(&creds_path) { From 4d19dbdd6731de6b1dd46d3284b73d01e8e5f349 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Fri, 13 Feb 2026 12:52:32 +0100 Subject: [PATCH 3/7] gl-cli: remove repeated code Signed-off-by: Lagrang3 --- libs/gl-cli/src/node.rs | 102 +++------------------------------------- 1 file changed, 6 insertions(+), 96 deletions(-) diff --git a/libs/gl-cli/src/node.rs b/libs/gl-cli/src/node.rs index e045feb46..608d1348f 100644 --- a/libs/gl-cli/src/node.rs +++ b/libs/gl-cli/src/node.rs @@ -305,22 +305,7 @@ async fn newaddr_handler>(config: Config

) -> Result<()> { } async fn stop>(config: Config

) -> Result<()> { - let creds_path = config.data_dir.as_ref().join(CREDENTIALS_FILE_NAME); - let creds = match util::read_credentials(&creds_path) { - Some(c) => c, - None => { - return Err(Error::CredentialsNotFoundError(format!( - "could not read from {}", - creds_path.display() - ))) - } - }; - - let scheduler = gl_client::scheduler::Scheduler::new(config.network, creds) - .await - .map_err(Error::custom)?; - - let mut node: gl_client::node::ClnClient = scheduler.node().await.map_err(Error::custom)?; + let mut node: gl_client::node::ClnClient = get_node(config).await?; let res = node .stop(cln::StopRequest {}) .await @@ -331,22 +316,7 @@ async fn stop>(config: Config

) -> Result<()> { } async fn getinfo_handler>(config: Config

) -> Result<()> { - let creds_path = config.data_dir.as_ref().join(CREDENTIALS_FILE_NAME); - let creds = match util::read_credentials(&creds_path) { - Some(c) => c, - None => { - return Err(Error::CredentialsNotFoundError(format!( - "could not read from {}", - creds_path.display() - ))) - } - }; - - let scheduler = gl_client::scheduler::Scheduler::new(config.network, creds) - .await - .map_err(Error::custom)?; - - let mut node: gl_client::node::ClnClient = scheduler.node().await.map_err(Error::custom)?; + let mut node: gl_client::node::ClnClient = get_node(config).await?; let res = node .getinfo(cln::GetinfoRequest {}) .await @@ -367,22 +337,7 @@ async fn invoice_handler>( cltv: Option, deschashonly: Option, ) -> Result<()> { - let creds_path = config.data_dir.as_ref().join(CREDENTIALS_FILE_NAME); - let creds = match util::read_credentials(&creds_path) { - Some(c) => c, - None => { - return Err(Error::CredentialsNotFoundError(format!( - "could not read from {}", - creds_path.display() - ))) - } - }; - - let scheduler = gl_client::scheduler::Scheduler::new(config.network, creds) - .await - .map_err(Error::custom)?; - - let mut node: gl_client::node::ClnClient = scheduler.node().await.map_err(Error::custom)?; + let mut node: gl_client::node::ClnClient = get_node(config).await?; let res = node .invoice(cln::InvoiceRequest { exposeprivatechannels: vec![], @@ -408,22 +363,7 @@ async fn connect_handler>( host: Option, port: Option, ) -> Result<()> { - let creds_path = config.data_dir.as_ref().join(CREDENTIALS_FILE_NAME); - let creds = match util::read_credentials(&creds_path) { - Some(c) => c, - None => { - return Err(Error::CredentialsNotFoundError(format!( - "could not read from {}", - creds_path.display() - ))) - } - }; - - let scheduler = gl_client::scheduler::Scheduler::new(config.network, creds) - .await - .map_err(Error::custom)?; - - let mut node: gl_client::node::ClnClient = scheduler.node().await.map_err(Error::custom)?; + let mut node: gl_client::node::ClnClient = get_node(config).await?; let res = node .connect_peer(cln::ConnectRequest { id, host, port }) .await @@ -439,22 +379,7 @@ async fn listpays_handler>( payment_hash: Option>, status: Option, ) -> Result<()> { - let creds_path = config.data_dir.as_ref().join(CREDENTIALS_FILE_NAME); - let creds = match util::read_credentials(&creds_path) { - Some(c) => c, - None => { - return Err(Error::CredentialsNotFoundError(format!( - "could not read from {}", - creds_path.display() - ))) - } - }; - - let scheduler = gl_client::scheduler::Scheduler::new(config.network, creds) - .await - .map_err(Error::custom)?; - - let mut node: gl_client::node::ClnClient = scheduler.node().await.map_err(Error::custom)?; + let mut node: gl_client::node::ClnClient = get_node(config).await?; let res = node .list_pays(cln::ListpaysRequest { index: None, @@ -486,22 +411,7 @@ async fn pay_handler>( maxfee: Option, description: Option, ) -> Result<()> { - let creds_path = config.data_dir.as_ref().join(CREDENTIALS_FILE_NAME); - let creds = match util::read_credentials(&creds_path) { - Some(c) => c, - None => { - return Err(Error::CredentialsNotFoundError(format!( - "could not read from {}", - creds_path.display() - ))) - } - }; - - let scheduler = gl_client::scheduler::Scheduler::new(config.network, creds) - .await - .map_err(Error::custom)?; - - let mut node: gl_client::node::ClnClient = scheduler.node().await.map_err(Error::custom)?; + let mut node: gl_client::node::ClnClient = get_node(config).await?; let res = node .pay(cln::PayRequest { bolt11, From 253a0c8b7764c6a599c3d97341c5998dfbfae1b6 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Tue, 17 Feb 2026 12:07:28 +0100 Subject: [PATCH 4/7] glcli: add listfunds command Signed-off-by: Lagrang3 --- libs/gl-cli/src/node.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libs/gl-cli/src/node.rs b/libs/gl-cli/src/node.rs index 608d1348f..f15f14fec 100644 --- a/libs/gl-cli/src/node.rs +++ b/libs/gl-cli/src/node.rs @@ -100,6 +100,8 @@ pub enum Command { }, /// Generates a new bitcoin address to receive funds Newaddr, + /// List available funds, both on-chain and payment channels + Listfunds, /// Stop the node Stop, } @@ -197,6 +199,7 @@ pub async fn command_handler>(cmd: Command, config: Config

) -> listpays_handler(config, bolt11, payment_hash, status).await } Command::Newaddr => newaddr_handler(config).await, + Command::Listfunds => listfunds_handler(config).await, Command::Stop => stop(config).await, } } @@ -304,6 +307,17 @@ async fn newaddr_handler>(config: Config

) -> Result<()> { Ok(()) } +async fn listfunds_handler>(config: Config

) -> Result<()> { + let mut node: gl_client::node::ClnClient = get_node(config).await?; + let res = node + .list_funds(cln::ListfundsRequest { spent: None }) + .await + .map_err(|e| Error::custom(e.message()))? + .into_inner(); + println!("{:?}", res); + Ok(()) +} + async fn stop>(config: Config

) -> Result<()> { let mut node: gl_client::node::ClnClient = get_node(config).await?; let res = node From 73aa96fd21ffb28aaa21b61fe2787f7a7b8e4f2a Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Tue, 17 Feb 2026 17:47:17 +0100 Subject: [PATCH 5/7] glcli: add withdraw command Signed-off-by: Lagrang3 --- libs/gl-cli/src/model.rs | 45 +++++++++++++++++++++++++++++++++++++++- libs/gl-cli/src/node.rs | 32 ++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/libs/gl-cli/src/model.rs b/libs/gl-cli/src/model.rs index b2a89daa8..c42a80ebb 100644 --- a/libs/gl-cli/src/model.rs +++ b/libs/gl-cli/src/model.rs @@ -1,4 +1,4 @@ -use gl_client::pb::cln::{self, amount_or_any}; +use gl_client::pb::cln::{self, amount_or_all, amount_or_any}; #[derive(Debug, Clone)] enum AmountOrAnyValue { @@ -40,3 +40,46 @@ impl Into for AmountOrAny { } } } + +#[derive(Debug, Clone)] +enum AmountSatOrAllValue { + All, + AmountSat(u64), +} + +#[derive(Debug, Clone)] +pub struct AmountSatOrAll { + value: AmountSatOrAllValue, +} + +impl From<&str> for AmountSatOrAll { + fn from(value: &str) -> Self { + if value == "all" { + return Self { + value: AmountSatOrAllValue::All, + }; + } else { + return match value.parse::() { + Ok(sat) => Self { + value: AmountSatOrAllValue::AmountSat(sat), + }, + Err(e) => panic!("{}", e), + }; + } + } +} + +impl Into for AmountSatOrAll { + fn into(self) -> cln::AmountOrAll { + match self.value { + AmountSatOrAllValue::All => cln::AmountOrAll { + value: Some(amount_or_all::Value::All(true)), + }, + AmountSatOrAllValue::AmountSat(sat) => cln::AmountOrAll { + value: Some(amount_or_all::Value::Amount(cln::Amount { + msat: sat * 1000, + })), + }, + } + } +} diff --git a/libs/gl-cli/src/node.rs b/libs/gl-cli/src/node.rs index f15f14fec..9f255c62a 100644 --- a/libs/gl-cli/src/node.rs +++ b/libs/gl-cli/src/node.rs @@ -102,6 +102,13 @@ pub enum Command { Newaddr, /// List available funds, both on-chain and payment channels Listfunds, + /// Send on-chain funds to external wallet + Withdraw { + #[arg(long, help = "Bitcoin address for the destination")] + destination: String, + #[arg(long, help = "Amount is sats or the string \"all\"")] + amount_sat: model::AmountSatOrAll, + }, /// Stop the node Stop, } @@ -200,6 +207,10 @@ pub async fn command_handler>(cmd: Command, config: Config

) -> } Command::Newaddr => newaddr_handler(config).await, Command::Listfunds => listfunds_handler(config).await, + Command::Withdraw { + destination, + amount_sat, + } => withdraw_handler(config, destination, amount_sat).await, Command::Stop => stop(config).await, } } @@ -318,6 +329,27 @@ async fn listfunds_handler>(config: Config

) -> Result<()> { Ok(()) } +async fn withdraw_handler>( + config: Config

, + destination: String, + amount_sat: model::AmountSatOrAll, +) -> Result<()> { + let mut node: gl_client::node::ClnClient = get_node(config).await?; + let res = node + .withdraw(cln::WithdrawRequest { + destination: destination, + satoshi: Some(amount_sat.into()), + feerate: None, + minconf: Some(0), + utxos: vec![], + }) + .await + .map_err(|e| Error::custom(e.message()))? + .into_inner(); + println!("{:?}", res); + Ok(()) +} + async fn stop>(config: Config

) -> Result<()> { let mut node: gl_client::node::ClnClient = get_node(config).await?; let res = node From 8b85f32758aab3c6fdfbb57ab4cca5ae4dbf65d1 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Wed, 18 Feb 2026 08:08:10 +0100 Subject: [PATCH 6/7] glcli: add close [channel] command Signed-off-by: Lagrang3 --- libs/gl-cli/src/node.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/libs/gl-cli/src/node.rs b/libs/gl-cli/src/node.rs index 9f255c62a..ae0c60464 100644 --- a/libs/gl-cli/src/node.rs +++ b/libs/gl-cli/src/node.rs @@ -109,6 +109,11 @@ pub enum Command { #[arg(long, help = "Amount is sats or the string \"all\"")] amount_sat: model::AmountSatOrAll, }, + /// Close a channel with peer + Close { + #[arg(long, help = "Peer id, channel id or short channel id")] + id: String, + }, /// Stop the node Stop, } @@ -211,6 +216,7 @@ pub async fn command_handler>(cmd: Command, config: Config

) -> destination, amount_sat, } => withdraw_handler(config, destination, amount_sat).await, + Command::Close { id } => close_handler(config, id).await, Command::Stop => stop(config).await, } } @@ -350,6 +356,25 @@ async fn withdraw_handler>( Ok(()) } +async fn close_handler>(config: Config

, id: String) -> Result<()> { + let mut node: gl_client::node::ClnClient = get_node(config).await?; + let res = node + .close(cln::CloseRequest { + id: id, + unilateraltimeout: None, + destination: None, + fee_negotiation_step: None, + wrong_funding: None, + force_lease_closed: None, + feerange: vec![], + }) + .await + .map_err(|e| Error::custom(e.message()))? + .into_inner(); + println!("{:?}", res); + Ok(()) +} + async fn stop>(config: Config

) -> Result<()> { let mut node: gl_client::node::ClnClient = get_node(config).await?; let res = node From 961c1c7f02af624998fb37aaa22d61f0abb2ddd4 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Wed, 18 Feb 2026 08:23:51 +0100 Subject: [PATCH 7/7] glcli: add fundchannel command Signed-off-by: Lagrang3 --- libs/gl-cli/src/node.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/libs/gl-cli/src/node.rs b/libs/gl-cli/src/node.rs index ae0c60464..e71147dea 100644 --- a/libs/gl-cli/src/node.rs +++ b/libs/gl-cli/src/node.rs @@ -109,6 +109,13 @@ pub enum Command { #[arg(long, help = "Amount is sats or the string \"all\"")] amount_sat: model::AmountSatOrAll, }, + /// Open a channel with peer + Fundchannel { + #[arg(long, help = "Peer id")] + id: String, + #[arg(long, help = "Amount in sats or the string \"all\"")] + amount_sat: model::AmountSatOrAll, + }, /// Close a channel with peer Close { #[arg(long, help = "Peer id, channel id or short channel id")] @@ -216,6 +223,9 @@ pub async fn command_handler>(cmd: Command, config: Config

) -> destination, amount_sat, } => withdraw_handler(config, destination, amount_sat).await, + Command::Fundchannel { id, amount_sat } => { + fundchannel_handler(config, id, amount_sat).await + } Command::Close { id } => close_handler(config, id).await, Command::Stop => stop(config).await, } @@ -356,6 +366,37 @@ async fn withdraw_handler>( Ok(()) } +async fn fundchannel_handler>( + config: Config

, + id: String, + amount_sat: model::AmountSatOrAll, +) -> Result<()> { + let mut node: gl_client::node::ClnClient = get_node(config).await?; + let id_bytes = hex::FromHex::from_hex(&id) + .map_err(|e| Error::custom(format!("Invalid hex string: {id}. {e}")))?; + let res = node + .fund_channel(cln::FundchannelRequest { + id: id_bytes, + amount: Some(amount_sat.into()), + feerate: None, + announce: None, + minconf: None, + push_msat: None, + close_to: None, + request_amt: None, + compact_lease: None, + utxos: vec![], + mindepth: None, + reserve: None, + channel_type: vec![], + }) + .await + .map_err(|e| Error::custom(e.message()))? + .into_inner(); + println!("{:?}", res); + Ok(()) +} + async fn close_handler>(config: Config

, id: String) -> Result<()> { let mut node: gl_client::node::ClnClient = get_node(config).await?; let res = node