Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion libs/gl-cli/src/model.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -40,3 +40,46 @@ impl Into<cln::AmountOrAny> 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::<u64>() {
Ok(sat) => Self {
value: AmountSatOrAllValue::AmountSat(sat),
},
Err(e) => panic!("{}", e),
};
}
}
}

impl Into<cln::AmountOrAll> 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,
})),
},
}
}
}
241 changes: 148 additions & 93 deletions libs/gl-cli/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,29 @@ pub enum Command {
)]
status: Option<String>,
},
/// 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,
}
Expand Down Expand Up @@ -194,6 +217,16 @@ pub async fn command_handler<P: AsRef<Path>>(cmd: Command, config: Config<P>) ->

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,
}
}
Expand Down Expand Up @@ -227,6 +260,25 @@ async fn init_handler<P: AsRef<Path>>(config: Config<P>, mnemonic: Option<String
Ok(())
}

async fn get_node<P: AsRef<Path>>(config: Config<P>) -> Result<gl_client::node::ClnClient> {
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<P: AsRef<Path>>(config: Config<P>) -> Result<()> {
let creds_path = config.data_dir.as_ref().join(CREDENTIALS_FILE_NAME);
let creds = match util::read_credentials(&creds_path) {
Expand Down Expand Up @@ -271,49 +323,112 @@ async fn log<P: AsRef<Path>>(config: Config<P>) -> Result<()> {
Ok(())
}

async fn stop<P: AsRef<Path>>(config: Config<P>) -> 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<P: AsRef<Path>>(config: Config<P>) -> 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<P: AsRef<Path>>(config: Config<P>) -> 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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it'd be possible to annotate the protobuf messages with a JSON serialization, allowing us to return a machine readable JSON object on stdout (and log to stderr) so that we can for example pipe it into jq for scripting? Just wondering if that's somethign that would be easy 🤔

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, probably more of a follow-up than a blocker here.

Ok(())
}

let mut node: gl_client::node::ClnClient = scheduler.node().await.map_err(Error::custom)?;
async fn withdraw_handler<P: AsRef<Path>>(
config: Config<P>,
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();
println!("{:?}", res);
Ok(())
}

async fn getinfo_handler<P: AsRef<Path>>(config: Config<P>) -> 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<P: AsRef<Path>>(
config: Config<P>,
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<P: AsRef<Path>>(config: Config<P>, 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<P: AsRef<Path>>(config: Config<P>) -> 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<P: AsRef<Path>>(config: Config<P>) -> Result<()> {
let mut node: gl_client::node::ClnClient = get_node(config).await?;
let res = node
.getinfo(cln::GetinfoRequest {})
.await
Expand All @@ -334,22 +449,7 @@ async fn invoice_handler<P: AsRef<Path>>(
cltv: Option<u32>,
deschashonly: Option<bool>,
) -> 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![],
Expand All @@ -375,22 +475,7 @@ async fn connect_handler<P: AsRef<Path>>(
host: Option<String>,
port: Option<u32>,
) -> 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
Expand All @@ -406,22 +491,7 @@ async fn listpays_handler<P: AsRef<Path>>(
payment_hash: Option<Vec<u8>>,
status: Option<i32>,
) -> 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,
Expand Down Expand Up @@ -453,22 +523,7 @@ async fn pay_handler<P: AsRef<Path>>(
maxfee: Option<u64>,
description: Option<String>,
) -> 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,
Expand Down
Loading
Loading