mokshamint/lightning/
lnd.rs1use std::{
2 fmt::{self, Formatter},
3 path::PathBuf,
4 sync::Arc,
5};
6
7use crate::{
8 error::MokshaMintError,
9 model::{CreateInvoiceResult, PayInvoiceResult},
10 url_serialize::{deserialize_url, serialize_url},
11};
12use async_trait::async_trait;
13use clap::Parser;
14use fedimint_tonic_lnd::Client;
15use serde::{Deserialize, Serialize};
16use tokio::sync::{MappedMutexGuard, Mutex, MutexGuard};
17use tracing::info;
18use url::Url;
19
20use super::Lightning;
21
22#[derive(Deserialize, Serialize, Debug, Clone, Default, Parser)]
23pub struct LndLightningSettings {
24 #[clap(long, env = "MINT_LND_GRPC_HOST")]
25 #[serde(serialize_with = "serialize_url", deserialize_with = "deserialize_url")]
26 pub grpc_host: Option<Url>,
27
28 #[clap(long, env = "MINT_LND_TLS_CERT_PATH")]
29 pub tls_cert_path: Option<PathBuf>,
30
31 #[clap(long, env = "MINT_LND_MACAROON_PATH")]
32 pub macaroon_path: Option<PathBuf>,
33}
34impl fmt::Display for LndLightningSettings {
35 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
36 write!(
37 f,
38 "grpc_host: {}, tls_cert_path: {}, macaroon_path: {}",
39 self.grpc_host.as_ref().unwrap(),
40 self.tls_cert_path
41 .as_ref()
42 .unwrap() .to_str()
44 .unwrap_or_default(),
45 self.macaroon_path
46 .as_ref()
47 .unwrap()
48 .to_str()
49 .unwrap_or_default()
50 )
51 }
52}
53
54pub struct LndLightning(Arc<Mutex<Client>>);
55
56impl LndLightning {
57 pub async fn new(
58 address: Url,
59 cert_file: &PathBuf,
60 macaroon_file: &PathBuf,
61 ) -> Result<Self, MokshaMintError> {
62 let client =
63 fedimint_tonic_lnd::connect(address.to_string(), cert_file, &macaroon_file).await;
64
65 Ok(Self(Arc::new(Mutex::new(
66 client.map_err(MokshaMintError::ConnectError)?,
67 ))))
68 }
69
70 pub async fn client_lock(
71 &self,
72 ) -> Result<MappedMutexGuard<'_, fedimint_tonic_lnd::LightningClient>, MokshaMintError> {
73 let guard = self.0.lock().await;
74 Ok(MutexGuard::map(guard, |client| client.lightning()))
75 }
76}
77
78#[allow(implied_bounds_entailment)]
79#[async_trait]
80impl Lightning for LndLightning {
81 async fn is_invoice_paid(&self, payment_request: String) -> Result<bool, MokshaMintError> {
82 let invoice = self.decode_invoice(payment_request).await?;
83 let payment_hash = invoice.payment_hash();
84 let invoice_request = fedimint_tonic_lnd::lnrpc::PaymentHash {
85 r_hash: payment_hash.to_vec(),
86 ..Default::default()
87 };
88
89 let invoice = self
90 .client_lock()
91 .await?
92 .lookup_invoice(fedimint_tonic_lnd::tonic::Request::new(invoice_request))
93 .await?
94 .into_inner();
95
96 Ok(invoice.state == fedimint_tonic_lnd::lnrpc::invoice::InvoiceState::Settled as i32)
97 }
98
99 async fn create_invoice(&self, amount: u64) -> Result<CreateInvoiceResult, MokshaMintError> {
100 let invoice_request = fedimint_tonic_lnd::lnrpc::Invoice {
101 value: amount as i64,
102 ..Default::default()
103 };
104
105 let invoice = self
106 .client_lock()
107 .await?
108 .add_invoice(fedimint_tonic_lnd::tonic::Request::new(invoice_request))
109 .await?
110 .into_inner();
111
112 Ok(CreateInvoiceResult {
113 payment_hash: invoice.r_hash,
114 payment_request: invoice.payment_request,
115 })
116 }
117
118 async fn pay_invoice(
119 &self,
120 payment_request: String,
121 ) -> Result<PayInvoiceResult, MokshaMintError> {
122 let pay_req = fedimint_tonic_lnd::lnrpc::SendRequest {
123 payment_request,
124 ..Default::default()
125 };
126 let payment_response = self
127 .client_lock()
128 .await?
129 .send_payment_sync(fedimint_tonic_lnd::tonic::Request::new(pay_req))
130 .await?
131 .into_inner();
132
133 let total_fees = payment_response
134 .payment_route
135 .map_or(0, |route| route.total_fees_msat / 1_000) as u64;
136
137 info!("lnd total_fees: {}", total_fees);
138
139 Ok(PayInvoiceResult {
140 payment_hash: hex::encode(payment_response.payment_hash),
141 total_fees,
142 })
143 }
144}