use std::str::FromStr;
use anyhow::Context;
use bitcoin::Amount;
use clap;
use lightning::offers::offer::Offer;
use lightning_invoice::Bolt11Invoice;
use lnurl::lightning_address::LightningAddress;
use log::info;
use ark::lightning::{PaymentHash, Preimage};
use bark::Wallet;
use bark_json::cli::{InvoiceInfo, LightningReceiveInfo, LightningSendInfo};
use bark_cli::util::output_json;
#[derive(clap::Subcommand)]
pub enum LightningCommand {
#[command(subcommand)]
Pay(PayCommand),
#[command(subcommand)]
Receive(ReceiveCommand),
#[command()]
Invoice {
amount: Amount,
#[arg(long)]
wait: bool,
#[arg(long)]
token: Option<String>,
},
#[command()]
Invoices,
#[command()]
Claim {
payment: Option<String>,
#[arg(long)]
wait: bool,
#[arg(long)]
no_sync: bool,
#[arg(long)]
token: Option<String>,
},
}
#[derive(clap::Subcommand)]
pub enum PayCommand {
#[command()]
Invoice {
invoice: String,
amount: Option<Amount>,
comment: Option<String>,
#[arg(long)]
no_sync: bool,
#[arg(long)]
wait: bool,
},
#[command()]
Status {
#[clap(flatten)]
filter_args: LightningStatusFilterGroup,
#[arg(long)]
no_sync: bool,
},
}
#[derive(clap::Subcommand)]
pub enum ReceiveCommand {
#[command()]
Status {
#[clap(flatten)]
filter_args: LightningStatusFilterGroup,
#[arg(long)]
no_sync: bool,
},
}
#[derive(clap::Args)]
#[group(required = true, multiple = false)]
pub struct LightningStatusFilterGroup {
filter: Option<String>,
#[arg(long)]
preimage: Option<Preimage>,
}
fn payment_hash_from_filter(filter: &str) -> anyhow::Result<PaymentHash> {
if let Ok(h) = PaymentHash::from_str(&filter) {
Ok(h)
} else if let Ok(i) = Bolt11Invoice::from_str(&filter) {
Ok(i.into())
} else {
bail!("filter is not valid payment hash nor invoice");
}
}
pub async fn execute_lightning_command(
lightning_command: LightningCommand,
wallet: &mut Wallet,
) -> anyhow::Result<()> {
match lightning_command {
LightningCommand::Pay(pay_cmd) => {
execute_pay_command(pay_cmd, wallet).await?;
},
LightningCommand::Receive(receive_cmd) => {
execute_receive_command(receive_cmd, wallet).await?;
},
LightningCommand::Invoice { amount, wait, token } => {
let invoice = wallet.bolt11_invoice(amount).await?;
output_json(&InvoiceInfo { invoice: invoice.to_string() });
if wait {
let token = token.as_ref().map(|c| c.as_str());
wallet.try_claim_lightning_receive(invoice.into(), true, token).await?;
}
},
LightningCommand::Invoices => {
let mut receives = wallet.pending_lightning_receives().await?;
receives.reverse();
output_json(&receives.into_iter().map(LightningReceiveInfo::from).collect::<Vec<_>>());
},
LightningCommand::Claim { payment, wait, no_sync, token } => {
if !no_sync {
info!("Syncing wallet...");
wallet.sync().await;
}
if let Some(payment) = payment {
let payment_hash = match PaymentHash::from_str(&payment) {
Ok(h) => h,
Err(_) => match Bolt11Invoice::from_str(&payment) {
Ok(i) => i.into(),
Err(_) => bail!("invalid invoice or payment hash"),
}
};
let token = token.as_ref().map(|c| c.as_str());
wallet.try_claim_lightning_receive(payment_hash, wait, token).await?;
} else {
info!("no invoice provided, trying to claim all open invoices");
wallet.try_claim_all_lightning_receives(wait).await?;
}
},
}
Ok(())
}
async fn execute_pay_command(
pay_command: PayCommand,
wallet: &mut Wallet,
) -> anyhow::Result<()> {
match pay_command {
PayCommand::Invoice { invoice, amount, comment, no_sync, wait } => {
if !no_sync {
info!("Syncing wallet...");
wallet.sync().await;
}
let payment = if let Ok(invoice) = Bolt11Invoice::from_str(&invoice) {
if comment.is_some() {
bail!("comment is not supported for BOLT-11 invoices");
}
wallet.pay_lightning_invoice(invoice, amount).await?
} else if let Ok(offer) = Offer::from_str(&invoice) {
if comment.is_some() {
bail!("comment is not supported for BOLT-12 offers");
}
wallet.pay_lightning_offer(offer, amount).await?
} else if let Ok(lnaddr) = LightningAddress::from_str(&invoice) {
let amount = amount.context("amount is required for Lightning addresses")?;
wallet.pay_lightning_address(&lnaddr, amount, comment).await?
} else {
bail!("argument is not a valid BOLT-11 invoice, BOLT-12 offer or \
Lightning address");
};
if wait {
let payment_hash = payment.invoice.payment_hash();
wallet.check_lightning_payment(payment_hash, true).await?;
}
},
PayCommand::Status { filter_args: LightningStatusFilterGroup { filter, preimage }, no_sync } => {
if !no_sync {
info!("Syncing wallet...");
wallet.sync().await;
}
let payment_hash = match (filter, preimage) {
(Some(filter), None) => payment_hash_from_filter(&filter)?,
(None, Some(p)) => p.into(),
(None, None) => bail!("need to provide a filter"),
(Some(_), Some(_)) => bail!("cannot provide both filter and preimage"),
};
if let Some(ret) = wallet.check_lightning_payment(payment_hash, false).await? {
output_json(&LightningSendInfo::from(ret));
} else {
info!("No outgoing payment found for this payment hash");
}
},
}
Ok(())
}
async fn execute_receive_command(
receive_command: ReceiveCommand,
wallet: &mut Wallet,
) -> anyhow::Result<()> {
match receive_command {
ReceiveCommand::Status { filter_args: LightningStatusFilterGroup { filter, preimage }, no_sync } => {
if !no_sync {
info!("Syncing wallet...");
wallet.sync().await;
}
let payment_hash = match (filter, preimage) {
(Some(filter), None) => payment_hash_from_filter(&filter)?,
(None, Some(p)) => p.into(),
(None, None) => bail!("need to provide a filter"),
(Some(_), Some(_)) => bail!("cannot provide both filter and preimage"),
};
if let Some(ret) = wallet.lightning_receive_status(payment_hash).await? {
output_json(&LightningReceiveInfo::from(ret));
} else {
info!("No incoming payment found for this payment hash");
}
},
}
Ok(())
}