lib.rs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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,
  76. melt_settings: self.melt_settings,
  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. .map_err(|_| Error::PaymentFailed)?
  168. .into_inner();
  169. let total_amount = payment_response
  170. .payment_route
  171. .map_or(0, |route| route.total_amt_msat / MSAT_IN_SAT as i64)
  172. as u64;
  173. let (status, payment_preimage) = match total_amount == 0 {
  174. true => (MeltQuoteState::Unpaid, None),
  175. false => (
  176. MeltQuoteState::Paid,
  177. Some(hex::encode(payment_response.payment_preimage)),
  178. ),
  179. };
  180. Ok(PayInvoiceResponse {
  181. payment_hash: hex::encode(payment_response.payment_hash),
  182. payment_preimage,
  183. status,
  184. total_spent: total_amount.into(),
  185. unit: CurrencyUnit::Sat,
  186. })
  187. }
  188. async fn create_invoice(
  189. &self,
  190. amount: Amount,
  191. unit: &CurrencyUnit,
  192. description: String,
  193. unix_expiry: u64,
  194. ) -> Result<CreateInvoiceResponse, Self::Err> {
  195. let time_now = unix_time();
  196. assert!(unix_expiry > time_now);
  197. let amount = to_unit(amount, unit, &CurrencyUnit::Msat)?;
  198. let invoice_request = fedimint_tonic_lnd::lnrpc::Invoice {
  199. value_msat: u64::from(amount) as i64,
  200. memo: description,
  201. ..Default::default()
  202. };
  203. let invoice = self
  204. .client
  205. .lock()
  206. .await
  207. .lightning()
  208. .add_invoice(fedimint_tonic_lnd::tonic::Request::new(invoice_request))
  209. .await
  210. .unwrap()
  211. .into_inner();
  212. let bolt11 = Bolt11Invoice::from_str(&invoice.payment_request)?;
  213. Ok(CreateInvoiceResponse {
  214. request_lookup_id: bolt11.payment_hash().to_string(),
  215. request: bolt11,
  216. expiry: Some(unix_expiry),
  217. })
  218. }
  219. async fn check_invoice_status(
  220. &self,
  221. request_lookup_id: &str,
  222. ) -> Result<MintQuoteState, Self::Err> {
  223. let invoice_request = fedimint_tonic_lnd::lnrpc::PaymentHash {
  224. r_hash: hex::decode(request_lookup_id).unwrap(),
  225. ..Default::default()
  226. };
  227. let invoice = self
  228. .client
  229. .lock()
  230. .await
  231. .lightning()
  232. .lookup_invoice(fedimint_tonic_lnd::tonic::Request::new(invoice_request))
  233. .await
  234. .unwrap()
  235. .into_inner();
  236. match invoice.state {
  237. // Open
  238. 0 => Ok(MintQuoteState::Unpaid),
  239. // Settled
  240. 1 => Ok(MintQuoteState::Paid),
  241. // Canceled
  242. 2 => Ok(MintQuoteState::Unpaid),
  243. // Accepted
  244. 3 => Ok(MintQuoteState::Unpaid),
  245. _ => Err(Self::Err::Anyhow(anyhow!("Invalid status"))),
  246. }
  247. }
  248. }