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 219a017a0..e71147dea 100644 --- a/libs/gl-cli/src/node.rs +++ b/libs/gl-cli/src/node.rs @@ -98,6 +98,29 @@ pub enum Command { )] status: Option, }, + /// Generates a new bitcoin address to receive funds + 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, + }, + /// 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")] + id: String, + }, /// Stop the node Stop, } @@ -194,6 +217,16 @@ 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::Withdraw { + 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, } } @@ -227,6 +260,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,25 +323,42 @@ async fn log>(config: Config

) -> Result<()> { 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) { - Some(c) => c, - None => { - return Err(Error::CredentialsNotFoundError(format!( - "could not read from {}", - creds_path.display() - ))) - } - }; +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(()) +} - let scheduler = gl_client::scheduler::Scheduler::new(config.network, creds) +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(Error::custom)?; + .map_err(|e| Error::custom(e.message()))? + .into_inner(); + println!("{:?}", res); + Ok(()) +} - let mut node: gl_client::node::ClnClient = scheduler.node().await.map_err(Error::custom)?; +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 - .stop(cln::StopRequest {}) + .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(); @@ -297,23 +366,69 @@ async fn stop>(config: Config

) -> Result<()> { Ok(()) } -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() - ))) - } - }; +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(()) +} - let scheduler = gl_client::scheduler::Scheduler::new(config.network, creds) +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(Error::custom)?; + .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 + .stop(cln::StopRequest {}) + .await + .map_err(|e| Error::custom(e.message()))? + .into_inner(); + println!("{:?}", res); + Ok(()) +} - let mut node: gl_client::node::ClnClient = scheduler.node().await.map_err(Error::custom)?; +async fn getinfo_handler>(config: Config

) -> Result<()> { + let mut node: gl_client::node::ClnClient = get_node(config).await?; let res = node .getinfo(cln::GetinfoRequest {}) .await @@ -334,22 +449,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![], @@ -375,22 +475,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 @@ -406,22 +491,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, @@ -453,22 +523,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, 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(), })); }