lib.rs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. //! CDK lightning backend for LND
  2. // Copyright (c) 2023 Steffen (MIT)
  3. #![warn(missing_docs)]
  4. #![warn(rustdoc::bare_urls)]
  5. use std::path::PathBuf;
  6. use std::pin::Pin;
  7. use std::str::FromStr;
  8. use std::sync::Arc;
  9. use anyhow::anyhow;
  10. use async_trait::async_trait;
  11. use cdk::amount::Amount;
  12. use cdk::cdk_lightning::{
  13. self, to_unit, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse,
  14. Settings, MSAT_IN_SAT,
  15. };
  16. use cdk::mint::FeeReserve;
  17. use cdk::nuts::{
  18. CurrencyUnit, MeltMethodSettings, MeltQuoteBolt11Request, MeltQuoteState, MintMethodSettings,
  19. MintQuoteState,
  20. };
  21. use cdk::util::{hex, unix_time};
  22. use cdk::{mint, Bolt11Invoice};
  23. use error::Error;
  24. use fedimint_tonic_lnd::lnrpc::fee_limit::Limit;
  25. use fedimint_tonic_lnd::lnrpc::FeeLimit;
  26. use fedimint_tonic_lnd::Client;
  27. use futures::{Stream, StreamExt};
  28. use tokio::sync::Mutex;
  29. pub mod error;
  30. /// Lnd mint backend
  31. #[derive(Clone)]
  32. pub struct Lnd {
  33. address: String,
  34. cert_file: PathBuf,
  35. macaroon_file: PathBuf,
  36. client: Arc<Mutex<Client>>,
  37. fee_reserve: FeeReserve,
  38. mint_settings: MintMethodSettings,
  39. melt_settings: MeltMethodSettings,
  40. }
  41. impl Lnd {
  42. /// Create new [`Lnd`]
  43. pub async fn new(
  44. address: String,
  45. cert_file: PathBuf,
  46. macaroon_file: PathBuf,
  47. fee_reserve: FeeReserve,
  48. mint_settings: MintMethodSettings,
  49. melt_settings: MeltMethodSettings,
  50. ) -> Result<Self, Error> {
  51. let client = fedimint_tonic_lnd::connect(address.to_string(), &cert_file, &macaroon_file)
  52. .await
  53. .map_err(|err| {
  54. tracing::error!("Connection error: {}", err.to_string());
  55. Error::Connection
  56. })?;
  57. Ok(Self {
  58. address,
  59. cert_file,
  60. macaroon_file,
  61. client: Arc::new(Mutex::new(client)),
  62. fee_reserve,
  63. mint_settings,
  64. melt_settings,
  65. })
  66. }
  67. }
  68. #[async_trait]
  69. impl MintLightning for Lnd {
  70. type Err = cdk_lightning::Error;
  71. fn get_settings(&self) -> Settings {
  72. Settings {
  73. mpp: true,
  74. unit: CurrencyUnit::Msat,
  75. mint_settings: self.mint_settings.clone(),
  76. melt_settings: self.melt_settings.clone(),
  77. invoice_description: true,
  78. }
  79. }
  80. async fn wait_any_invoice(
  81. &self,
  82. ) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
  83. let mut client =
  84. fedimint_tonic_lnd::connect(self.address.clone(), &self.cert_file, &self.macaroon_file)
  85. .await
  86. .map_err(|_| Error::Connection)?;
  87. let stream_req = fedimint_tonic_lnd::lnrpc::InvoiceSubscription {
  88. add_index: 0,
  89. settle_index: 0,
  90. };
  91. let stream = client
  92. .lightning()
  93. .subscribe_invoices(stream_req)
  94. .await
  95. .unwrap()
  96. .into_inner();
  97. Ok(futures::stream::unfold(stream, |mut stream| async move {
  98. match stream.message().await {
  99. Ok(Some(msg)) => {
  100. if msg.state == 1 {
  101. Some((hex::encode(msg.r_hash), stream))
  102. } else {
  103. None
  104. }
  105. }
  106. Ok(None) => None, // End of stream
  107. Err(_) => None, // Handle errors gracefully, ends the stream on error
  108. }
  109. })
  110. .boxed())
  111. }
  112. async fn get_payment_quote(
  113. &self,
  114. melt_quote_request: &MeltQuoteBolt11Request,
  115. ) -> Result<PaymentQuoteResponse, Self::Err> {
  116. let invoice_amount_msat = melt_quote_request
  117. .request
  118. .amount_milli_satoshis()
  119. .ok_or(Error::UnknownInvoiceAmount)?;
  120. let amount = to_unit(
  121. invoice_amount_msat,
  122. &CurrencyUnit::Msat,
  123. &melt_quote_request.unit,
  124. )?;
  125. let relative_fee_reserve =
  126. (self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;
  127. let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
  128. let fee = match relative_fee_reserve > absolute_fee_reserve {
  129. true => relative_fee_reserve,
  130. false => absolute_fee_reserve,
  131. };
  132. Ok(PaymentQuoteResponse {
  133. request_lookup_id: melt_quote_request.request.payment_hash().to_string(),
  134. amount,
  135. fee: fee.into(),
  136. state: MeltQuoteState::Unpaid,
  137. })
  138. }
  139. async fn pay_invoice(
  140. &self,
  141. melt_quote: mint::MeltQuote,
  142. partial_amount: Option<Amount>,
  143. max_fee: Option<Amount>,
  144. ) -> Result<PayInvoiceResponse, Self::Err> {
  145. let payment_request = melt_quote.request;
  146. let pay_req = fedimint_tonic_lnd::lnrpc::SendRequest {
  147. payment_request,
  148. fee_limit: max_fee.map(|f| {
  149. let limit = Limit::Fixed(u64::from(f) as i64);
  150. FeeLimit { limit: Some(limit) }
  151. }),
  152. amt_msat: partial_amount
  153. .map(|a| {
  154. let msat = to_unit(a, &melt_quote.unit, &CurrencyUnit::Msat).unwrap();
  155. u64::from(msat) as i64
  156. })
  157. .unwrap_or_default(),
  158. ..Default::default()
  159. };
  160. let payment_response = self
  161. .client
  162. .lock()
  163. .await
  164. .lightning()
  165. .send_payment_sync(fedimint_tonic_lnd::tonic::Request::new(pay_req))
  166. .await
  167. .unwrap()
  168. .into_inner();
  169. let total_spent = payment_response
  170. .payment_route
  171. .map_or(0, |route| route.total_fees_msat / MSAT_IN_SAT as i64)
  172. as u64;
  173. Ok(PayInvoiceResponse {
  174. payment_hash: hex::encode(payment_response.payment_hash),
  175. payment_preimage: Some(hex::encode(payment_response.payment_preimage)),
  176. status: MeltQuoteState::Paid,
  177. total_spent: total_spent.into(),
  178. unit: CurrencyUnit::Sat,
  179. })
  180. }
  181. async fn create_invoice(
  182. &self,
  183. amount: Amount,
  184. unit: &CurrencyUnit,
  185. description: String,
  186. unix_expiry: u64,
  187. ) -> Result<CreateInvoiceResponse, Self::Err> {
  188. let time_now = unix_time();
  189. assert!(unix_expiry > time_now);
  190. let amount = to_unit(amount, unit, &CurrencyUnit::Msat)?;
  191. let invoice_request = fedimint_tonic_lnd::lnrpc::Invoice {
  192. value_msat: u64::from(amount) as i64,
  193. memo: description,
  194. ..Default::default()
  195. };
  196. let invoice = self
  197. .client
  198. .lock()
  199. .await
  200. .lightning()
  201. .add_invoice(fedimint_tonic_lnd::tonic::Request::new(invoice_request))
  202. .await
  203. .unwrap()
  204. .into_inner();
  205. let bolt11 = Bolt11Invoice::from_str(&invoice.payment_request)?;
  206. Ok(CreateInvoiceResponse {
  207. request_lookup_id: bolt11.payment_hash().to_string(),
  208. request: bolt11,
  209. expiry: Some(unix_expiry),
  210. })
  211. }
  212. async fn check_invoice_status(
  213. &self,
  214. request_lookup_id: &str,
  215. ) -> Result<MintQuoteState, Self::Err> {
  216. let invoice_request = fedimint_tonic_lnd::lnrpc::PaymentHash {
  217. r_hash: hex::decode(request_lookup_id).unwrap(),
  218. ..Default::default()
  219. };
  220. let invoice = self
  221. .client
  222. .lock()
  223. .await
  224. .lightning()
  225. .lookup_invoice(fedimint_tonic_lnd::tonic::Request::new(invoice_request))
  226. .await
  227. .unwrap()
  228. .into_inner();
  229. match invoice.state {
  230. // Open
  231. 0 => Ok(MintQuoteState::Unpaid),
  232. // Settled
  233. 1 => Ok(MintQuoteState::Paid),
  234. // Canceled
  235. 2 => Ok(MintQuoteState::Unpaid),
  236. // Accepted
  237. 3 => Ok(MintQuoteState::Unpaid),
  238. _ => Err(Self::Err::Anyhow(anyhow!("Invalid status"))),
  239. }
  240. }
  241. }