lib.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. //! CDK lightning backend for CLN
  2. use std::path::PathBuf;
  3. use std::pin::Pin;
  4. use std::str::FromStr;
  5. use std::sync::Arc;
  6. use std::time::Duration;
  7. use async_trait::async_trait;
  8. use cdk::cdk_lightning::{
  9. self, to_unit, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse,
  10. Settings,
  11. };
  12. use cdk::mint::FeeReserve;
  13. use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState};
  14. use cdk::util::{hex, unix_time};
  15. use cdk::{mint, Bolt11Invoice};
  16. use cln_rpc::model::requests::{
  17. InvoiceRequest, ListinvoicesRequest, ListpaysRequest, PayRequest, WaitanyinvoiceRequest,
  18. };
  19. use cln_rpc::model::responses::{
  20. ListinvoicesInvoicesStatus, ListpaysPaysStatus, PayStatus, WaitanyinvoiceResponse,
  21. };
  22. use cln_rpc::model::Request;
  23. use cln_rpc::primitives::{Amount as CLN_Amount, AmountOrAny};
  24. use error::Error;
  25. use futures::{Stream, StreamExt};
  26. use tokio::sync::Mutex;
  27. use uuid::Uuid;
  28. pub mod error;
  29. #[derive(Clone)]
  30. pub struct Cln {
  31. rpc_socket: PathBuf,
  32. cln_client: Arc<Mutex<cln_rpc::ClnRpc>>,
  33. fee_reserve: FeeReserve,
  34. min_melt_amount: u64,
  35. max_melt_amount: u64,
  36. min_mint_amount: u64,
  37. max_mint_amount: u64,
  38. mint_enabled: bool,
  39. melt_enabled: bool,
  40. }
  41. impl Cln {
  42. pub async fn new(
  43. rpc_socket: PathBuf,
  44. fee_reserve: FeeReserve,
  45. min_melt_amount: u64,
  46. max_melt_amount: u64,
  47. min_mint_amount: u64,
  48. max_mint_amount: u64,
  49. ) -> Result<Self, Error> {
  50. let cln_client = cln_rpc::ClnRpc::new(&rpc_socket).await?;
  51. Ok(Self {
  52. rpc_socket,
  53. cln_client: Arc::new(Mutex::new(cln_client)),
  54. fee_reserve,
  55. min_mint_amount,
  56. max_mint_amount,
  57. min_melt_amount,
  58. max_melt_amount,
  59. mint_enabled: true,
  60. melt_enabled: true,
  61. })
  62. }
  63. }
  64. #[async_trait]
  65. impl MintLightning for Cln {
  66. type Err = cdk_lightning::Error;
  67. fn get_settings(&self) -> Settings {
  68. Settings {
  69. mpp: true,
  70. min_mint_amount: self.min_mint_amount,
  71. max_mint_amount: self.max_mint_amount,
  72. min_melt_amount: self.min_melt_amount,
  73. max_melt_amount: self.max_melt_amount,
  74. unit: CurrencyUnit::Msat,
  75. mint_enabled: self.mint_enabled,
  76. melt_enabled: self.melt_enabled,
  77. }
  78. }
  79. async fn wait_any_invoice(
  80. &self,
  81. ) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
  82. let last_pay_index = self.get_last_pay_index().await?;
  83. let cln_client = cln_rpc::ClnRpc::new(&self.rpc_socket).await?;
  84. Ok(futures::stream::unfold(
  85. (cln_client, last_pay_index),
  86. |(mut cln_client, mut last_pay_idx)| async move {
  87. loop {
  88. let invoice_res = cln_client
  89. .call(cln_rpc::Request::WaitAnyInvoice(WaitanyinvoiceRequest {
  90. timeout: None,
  91. lastpay_index: last_pay_idx,
  92. }))
  93. .await;
  94. let invoice: WaitanyinvoiceResponse = match invoice_res {
  95. Ok(invoice) => invoice,
  96. Err(e) => {
  97. tracing::warn!("Error fetching invoice: {e}");
  98. // Let's not spam CLN with requests on failure
  99. tokio::time::sleep(Duration::from_secs(1)).await;
  100. // Retry same request
  101. continue;
  102. }
  103. }
  104. .try_into()
  105. .expect("Wrong response from CLN");
  106. last_pay_idx = invoice.pay_index;
  107. break Some((invoice.label, (cln_client, last_pay_idx)));
  108. }
  109. },
  110. )
  111. .boxed())
  112. }
  113. async fn get_payment_quote(
  114. &self,
  115. melt_quote_request: &MeltQuoteBolt11Request,
  116. ) -> Result<PaymentQuoteResponse, Self::Err> {
  117. let invoice_amount_msat = melt_quote_request
  118. .request
  119. .amount_milli_satoshis()
  120. .ok_or(Error::UnknownInvoiceAmount)?;
  121. let amount = to_unit(
  122. invoice_amount_msat,
  123. &CurrencyUnit::Msat,
  124. &melt_quote_request.unit,
  125. )?;
  126. let relative_fee_reserve = (self.fee_reserve.percent_fee_reserve * 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,
  136. })
  137. }
  138. async fn pay_invoice(
  139. &self,
  140. melt_quote: mint::MeltQuote,
  141. partial_msats: Option<u64>,
  142. max_fee_msats: Option<u64>,
  143. ) -> Result<PayInvoiceResponse, Self::Err> {
  144. let mut cln_client = self.cln_client.lock().await;
  145. let pay_state =
  146. check_pay_invoice_status(&mut cln_client, melt_quote.request.to_string()).await?;
  147. match pay_state {
  148. MeltQuoteState::Paid => {
  149. tracing::debug!("Melt attempted on invoice already paid");
  150. return Err(Self::Err::InvoiceAlreadyPaid);
  151. }
  152. MeltQuoteState::Pending => {
  153. tracing::debug!("Melt attempted on invoice already pending");
  154. return Err(Self::Err::InvoicePaymentPending);
  155. }
  156. MeltQuoteState::Unpaid => (),
  157. }
  158. let cln_response = cln_client
  159. .call(Request::Pay(PayRequest {
  160. bolt11: melt_quote.request.to_string(),
  161. amount_msat: None,
  162. label: None,
  163. riskfactor: None,
  164. maxfeepercent: None,
  165. retry_for: None,
  166. maxdelay: None,
  167. exemptfee: None,
  168. localinvreqid: None,
  169. exclude: None,
  170. maxfee: max_fee_msats.map(CLN_Amount::from_msat),
  171. description: None,
  172. partial_msat: partial_msats.map(CLN_Amount::from_msat),
  173. }))
  174. .await
  175. .map_err(Error::from)?;
  176. let response = match cln_response {
  177. cln_rpc::Response::Pay(pay_response) => {
  178. let status = match pay_response.status {
  179. PayStatus::COMPLETE => MeltQuoteState::Paid,
  180. PayStatus::PENDING => MeltQuoteState::Pending,
  181. PayStatus::FAILED => MeltQuoteState::Unpaid,
  182. };
  183. PayInvoiceResponse {
  184. payment_preimage: Some(hex::encode(pay_response.payment_preimage.to_vec())),
  185. payment_hash: pay_response.payment_hash.to_string(),
  186. status,
  187. total_spent_msats: pay_response.amount_sent_msat.msat(),
  188. }
  189. }
  190. _ => {
  191. tracing::warn!("CLN returned wrong response kind");
  192. return Err(cdk_lightning::Error::from(Error::WrongClnResponse));
  193. }
  194. };
  195. Ok(response)
  196. }
  197. async fn create_invoice(
  198. &self,
  199. amount_msats: u64,
  200. description: String,
  201. unix_expiry: u64,
  202. ) -> Result<CreateInvoiceResponse, Self::Err> {
  203. let time_now = unix_time();
  204. assert!(unix_expiry > time_now);
  205. let mut cln_client = self.cln_client.lock().await;
  206. let label = Uuid::new_v4().to_string();
  207. let amount_msat = AmountOrAny::Amount(CLN_Amount::from_msat(amount_msats));
  208. let cln_response = cln_client
  209. .call(cln_rpc::Request::Invoice(InvoiceRequest {
  210. amount_msat,
  211. description,
  212. label: label.clone(),
  213. expiry: Some(unix_expiry - time_now),
  214. fallbacks: None,
  215. preimage: None,
  216. cltv: None,
  217. deschashonly: None,
  218. exposeprivatechannels: None,
  219. }))
  220. .await
  221. .map_err(Error::from)?;
  222. match cln_response {
  223. cln_rpc::Response::Invoice(invoice_res) => Ok(CreateInvoiceResponse {
  224. request_lookup_id: label,
  225. request: Bolt11Invoice::from_str(&invoice_res.bolt11)?,
  226. }),
  227. _ => {
  228. tracing::warn!("CLN returned wrong response kind");
  229. Err(Error::WrongClnResponse.into())
  230. }
  231. }
  232. }
  233. async fn check_invoice_status(
  234. &self,
  235. request_lookup_id: &str,
  236. ) -> Result<MintQuoteState, Self::Err> {
  237. let mut cln_client = self.cln_client.lock().await;
  238. let cln_response = cln_client
  239. .call(Request::ListInvoices(ListinvoicesRequest {
  240. payment_hash: None,
  241. label: Some(request_lookup_id.to_string()),
  242. invstring: None,
  243. offer_id: None,
  244. index: None,
  245. limit: None,
  246. start: None,
  247. }))
  248. .await
  249. .map_err(Error::from)?;
  250. let status = match cln_response {
  251. cln_rpc::Response::ListInvoices(invoice_response) => {
  252. match invoice_response.invoices.first() {
  253. Some(invoice_response) => {
  254. cln_invoice_status_to_mint_state(invoice_response.status)
  255. }
  256. None => {
  257. tracing::info!(
  258. "Check invoice called on unknown look up id: {}",
  259. request_lookup_id
  260. );
  261. return Err(Error::WrongClnResponse.into());
  262. }
  263. }
  264. }
  265. _ => {
  266. tracing::warn!("CLN returned wrong response kind");
  267. return Err(Error::Custom("CLN returned wrong response kind".to_string()).into());
  268. }
  269. };
  270. Ok(status)
  271. }
  272. }
  273. impl Cln {
  274. async fn get_last_pay_index(&self) -> Result<Option<u64>, Error> {
  275. let mut cln_client = self.cln_client.lock().await;
  276. let cln_response = cln_client
  277. .call(cln_rpc::Request::ListInvoices(ListinvoicesRequest {
  278. index: None,
  279. invstring: None,
  280. label: None,
  281. limit: None,
  282. offer_id: None,
  283. payment_hash: None,
  284. start: None,
  285. }))
  286. .await
  287. .map_err(Error::from)?;
  288. match cln_response {
  289. cln_rpc::Response::ListInvoices(invoice_res) => match invoice_res.invoices.last() {
  290. Some(last_invoice) => Ok(last_invoice.pay_index),
  291. None => Ok(None),
  292. },
  293. _ => {
  294. tracing::warn!("CLN returned wrong response kind");
  295. Err(Error::WrongClnResponse)
  296. }
  297. }
  298. }
  299. }
  300. fn cln_invoice_status_to_mint_state(status: ListinvoicesInvoicesStatus) -> MintQuoteState {
  301. match status {
  302. ListinvoicesInvoicesStatus::UNPAID => MintQuoteState::Unpaid,
  303. ListinvoicesInvoicesStatus::PAID => MintQuoteState::Paid,
  304. ListinvoicesInvoicesStatus::EXPIRED => MintQuoteState::Unpaid,
  305. }
  306. }
  307. async fn check_pay_invoice_status(
  308. cln_client: &mut cln_rpc::ClnRpc,
  309. bolt11: String,
  310. ) -> Result<MeltQuoteState, cdk_lightning::Error> {
  311. let cln_response = cln_client
  312. .call(Request::ListPays(ListpaysRequest {
  313. bolt11: Some(bolt11),
  314. payment_hash: None,
  315. status: None,
  316. }))
  317. .await
  318. .map_err(Error::from)?;
  319. let state = match cln_response {
  320. cln_rpc::Response::ListPays(pay_response) => {
  321. let pay = pay_response.pays.first();
  322. match pay {
  323. Some(pay) => match pay.status {
  324. ListpaysPaysStatus::COMPLETE => MeltQuoteState::Paid,
  325. ListpaysPaysStatus::PENDING => MeltQuoteState::Pending,
  326. ListpaysPaysStatus::FAILED => MeltQuoteState::Unpaid,
  327. },
  328. None => MeltQuoteState::Unpaid,
  329. }
  330. }
  331. _ => {
  332. tracing::warn!("CLN returned wrong response kind. When checking pay status");
  333. return Err(cdk_lightning::Error::from(Error::WrongClnResponse));
  334. }
  335. };
  336. Ok(state)
  337. }