use crate::lndrpc::lnrpc;
use lightning_invoice::{Bolt11Invoice, SignedRawBolt11Invoice};
use nwc::prelude::*;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use crate::lnclient;
#[derive(Debug, Clone)]
pub struct NWCOptions {
pub uri: String,
}
pub struct NWCWrapper {
pub client: Arc<Mutex<NostrWalletConnect>>,
}
impl NWCWrapper {
pub async fn new_client(
ln_client_config: &lnclient::LNClientConfig,
) -> Result<Arc<Mutex<dyn lnclient::LNClient>>, Box<dyn std::error::Error + Send + Sync>> {
let nwc_options = ln_client_config
.nwc_config
.clone()
.ok_or("LN_CLIENT_TYPE is NWC but nwc_config is missing")?;
let uri = NostrWalletConnectUri::parse(&nwc_options.uri)?;
let nwc = NostrWalletConnect::builder(uri)
.timeout(Duration::from_secs(60))
.build();
Ok(Arc::new(Mutex::new(NWCWrapper {
client: Arc::new(Mutex::new(nwc)),
})))
}
}
impl lnclient::LNClient for NWCWrapper {
fn add_invoice(
&self,
invoice: lnrpc::Invoice,
) -> Pin<
Box<
dyn Future<
Output = Result<
lnrpc::AddInvoiceResponse,
Box<dyn std::error::Error + Send + Sync>,
>,
> + Send,
>,
> {
let client = Arc::clone(&self.client);
Box::pin(async move {
let client = client.lock().await;
let params = MakeInvoiceRequest {
amount: invoice.value_msat as u64,
description: None,
description_hash: None,
expiry: None,
};
let response = match client.make_invoice(params).await {
Ok(res) => {
println!("make_invoice response payment_hash: {:?}", res.payment_hash);
let signed = res
.invoice
.parse::<SignedRawBolt11Invoice>()
.map_err(|e| format!("NWC returned an unparsable invoice: {:?}", e))?;
let decoded_invoice = Bolt11Invoice::from_signed(signed)
.map_err(|e| format!("NWC invoice failed validation: {:?}", e))?;
let payment_addr = decoded_invoice.payment_secret();
let payment_hash = res
.payment_hash
.ok_or("NWC make_invoice response has no payment_hash")?;
lnrpc::AddInvoiceResponse {
r_hash: hex::decode(&payment_hash)
.map_err(|e| format!("invalid payment_hash from NWC: {}", e))?,
payment_request: res.invoice,
add_index: 0,
payment_addr: payment_addr.0.to_vec(),
}
}
Err(e) => {
eprintln!("Error adding invoice: {:?}", e);
let boxed_error: Box<dyn std::error::Error + Send + Sync> = Box::new(e);
return Err(boxed_error);
}
};
Ok(response)
})
}
fn lookup_invoice(
&self,
payment_hash: Vec<u8>,
) -> Pin<
Box<
dyn Future<Output = Result<Option<Vec<u8>>, Box<dyn std::error::Error + Send + Sync>>>
+ Send,
>,
> {
let client = Arc::clone(&self.client);
Box::pin(async move {
let client = client.lock().await;
let request = LookupInvoiceRequest {
payment_hash: Some(hex::encode(&payment_hash)),
invoice: None,
};
let res = client
.lookup_invoice(request)
.await
.map_err(|e| format!("NWC lookup_invoice failed: {:?}", e))?;
if res.state != Some(TransactionState::Settled) && res.settled_at.is_none() {
return Ok(None);
}
match res.preimage.as_deref().filter(|p| !p.is_empty()) {
Some(preimage) => {
Ok(Some(hex::decode(preimage).map_err(|e| {
format!("invalid preimage from NWC: {}", e)
})?))
}
None => Err("NWC invoice settled but preimage missing".into()),
}
})
}
}