lib.rs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. //! CDK lightning backend for CLN
  2. #![doc = include_str!("../README.md")]
  3. #![warn(missing_docs)]
  4. #![warn(rustdoc::bare_urls)]
  5. use std::cmp::max;
  6. use std::path::PathBuf;
  7. use std::pin::Pin;
  8. use std::str::FromStr;
  9. use std::sync::atomic::{AtomicBool, Ordering};
  10. use std::sync::Arc;
  11. use async_trait::async_trait;
  12. use cdk::amount::{to_unit, Amount};
  13. use cdk::cdk_payment::{
  14. self, Bolt11Settings, CreateIncomingPaymentResponse, MakePaymentResponse, MintPayment,
  15. PaymentQuoteResponse,
  16. };
  17. use cdk::nuts::{CurrencyUnit, MeltOptions, MeltQuoteState, MintQuoteState};
  18. use cdk::types::FeeReserve;
  19. use cdk::util::{hex, unix_time};
  20. use cdk::{mint, Bolt11Invoice};
  21. use cln_rpc::model::requests::{
  22. InvoiceRequest, ListinvoicesRequest, ListpaysRequest, PayRequest, WaitanyinvoiceRequest,
  23. };
  24. use cln_rpc::model::responses::{
  25. ListinvoicesInvoices, ListinvoicesInvoicesStatus, ListpaysPaysStatus, PayStatus,
  26. WaitanyinvoiceStatus,
  27. };
  28. use cln_rpc::primitives::{Amount as CLN_Amount, AmountOrAny};
  29. use error::Error;
  30. use futures::{Stream, StreamExt};
  31. use serde_json::Value;
  32. use tokio::sync::Mutex;
  33. use tokio_util::sync::CancellationToken;
  34. use uuid::Uuid;
  35. pub mod error;
  36. /// CLN mint backend
  37. #[derive(Clone)]
  38. pub struct Cln {
  39. rpc_socket: PathBuf,
  40. cln_client: Arc<Mutex<cln_rpc::ClnRpc>>,
  41. fee_reserve: FeeReserve,
  42. wait_invoice_cancel_token: CancellationToken,
  43. wait_invoice_is_active: Arc<AtomicBool>,
  44. }
  45. impl Cln {
  46. /// Create new [`Cln`]
  47. pub async fn new(rpc_socket: PathBuf, fee_reserve: FeeReserve) -> Result<Self, Error> {
  48. let cln_client = cln_rpc::ClnRpc::new(&rpc_socket).await?;
  49. Ok(Self {
  50. rpc_socket,
  51. cln_client: Arc::new(Mutex::new(cln_client)),
  52. fee_reserve,
  53. wait_invoice_cancel_token: CancellationToken::new(),
  54. wait_invoice_is_active: Arc::new(AtomicBool::new(false)),
  55. })
  56. }
  57. }
  58. #[async_trait]
  59. impl MintPayment for Cln {
  60. type Err = cdk_payment::Error;
  61. async fn get_settings(&self) -> Result<Value, Self::Err> {
  62. Ok(serde_json::to_value(Bolt11Settings {
  63. mpp: true,
  64. unit: CurrencyUnit::Msat,
  65. invoice_description: true,
  66. })?)
  67. }
  68. /// Is wait invoice active
  69. fn is_wait_invoice_active(&self) -> bool {
  70. self.wait_invoice_is_active.load(Ordering::SeqCst)
  71. }
  72. /// Cancel wait invoice
  73. fn cancel_wait_invoice(&self) {
  74. self.wait_invoice_cancel_token.cancel()
  75. }
  76. async fn wait_any_incoming_payment(
  77. &self,
  78. ) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
  79. let last_pay_index = self.get_last_pay_index().await?;
  80. let cln_client = cln_rpc::ClnRpc::new(&self.rpc_socket).await?;
  81. let stream = futures::stream::unfold(
  82. (
  83. cln_client,
  84. last_pay_index,
  85. self.wait_invoice_cancel_token.clone(),
  86. Arc::clone(&self.wait_invoice_is_active),
  87. ),
  88. |(mut cln_client, mut last_pay_idx, cancel_token, is_active)| async move {
  89. // Set the stream as active
  90. is_active.store(true, Ordering::SeqCst);
  91. loop {
  92. let request = WaitanyinvoiceRequest {
  93. timeout: None,
  94. lastpay_index: last_pay_idx,
  95. };
  96. tokio::select! {
  97. _ = cancel_token.cancelled() => {
  98. // Set the stream as inactive
  99. is_active.store(false, Ordering::SeqCst);
  100. // End the stream
  101. return None;
  102. }
  103. result = cln_client.call_typed(&request) => {
  104. match result {
  105. Ok(invoice) => {
  106. // Check the status of the invoice
  107. // We only want to yield invoices that have been paid
  108. match invoice.status {
  109. WaitanyinvoiceStatus::PAID => (),
  110. WaitanyinvoiceStatus::EXPIRED => continue,
  111. }
  112. last_pay_idx = invoice.pay_index;
  113. let payment_hash = invoice.payment_hash.to_string();
  114. let request_look_up = match invoice.bolt12 {
  115. // If it is a bolt12 payment we need to get the offer_id as this is what we use as the request look up.
  116. // Since this is not returned in the wait any response,
  117. // we need to do a second query for it.
  118. Some(_) => {
  119. match fetch_invoice_by_payment_hash(
  120. &mut cln_client,
  121. &payment_hash,
  122. )
  123. .await
  124. {
  125. Ok(Some(invoice)) => {
  126. if let Some(local_offer_id) = invoice.local_offer_id {
  127. local_offer_id.to_string()
  128. } else {
  129. continue;
  130. }
  131. }
  132. Ok(None) => continue,
  133. Err(e) => {
  134. tracing::warn!(
  135. "Error fetching invoice by payment hash: {e}"
  136. );
  137. continue;
  138. }
  139. }
  140. }
  141. None => payment_hash,
  142. };
  143. return Some((request_look_up, (cln_client, last_pay_idx, cancel_token, is_active)));
  144. }
  145. Err(e) => {
  146. tracing::warn!("Error fetching invoice: {e}");
  147. is_active.store(false, Ordering::SeqCst);
  148. return None;
  149. }
  150. }
  151. }
  152. }
  153. }
  154. },
  155. )
  156. .boxed();
  157. Ok(stream)
  158. }
  159. async fn get_payment_quote(
  160. &self,
  161. request: &str,
  162. unit: &CurrencyUnit,
  163. options: Option<MeltOptions>,
  164. ) -> Result<PaymentQuoteResponse, Self::Err> {
  165. let bolt11 = Bolt11Invoice::from_str(request)?;
  166. let amount_msat = match options {
  167. Some(amount) => amount.amount_msat(),
  168. None => bolt11
  169. .amount_milli_satoshis()
  170. .ok_or(Error::UnknownInvoiceAmount)?
  171. .into(),
  172. };
  173. let amount = to_unit(amount_msat, &CurrencyUnit::Msat, unit)?;
  174. let relative_fee_reserve =
  175. (self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;
  176. let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
  177. let fee = max(relative_fee_reserve, absolute_fee_reserve);
  178. Ok(PaymentQuoteResponse {
  179. request_lookup_id: bolt11.payment_hash().to_string(),
  180. amount,
  181. fee: fee.into(),
  182. state: MeltQuoteState::Unpaid,
  183. })
  184. }
  185. async fn make_payment(
  186. &self,
  187. melt_quote: mint::MeltQuote,
  188. partial_amount: Option<Amount>,
  189. max_fee: Option<Amount>,
  190. ) -> Result<MakePaymentResponse, Self::Err> {
  191. let bolt11 = Bolt11Invoice::from_str(&melt_quote.request)?;
  192. let pay_state = self
  193. .check_outgoing_payment(&bolt11.payment_hash().to_string())
  194. .await?;
  195. match pay_state.status {
  196. MeltQuoteState::Unpaid | MeltQuoteState::Unknown | MeltQuoteState::Failed => (),
  197. MeltQuoteState::Paid => {
  198. tracing::debug!("Melt attempted on invoice already paid");
  199. return Err(Self::Err::InvoiceAlreadyPaid);
  200. }
  201. MeltQuoteState::Pending => {
  202. tracing::debug!("Melt attempted on invoice already pending");
  203. return Err(Self::Err::InvoicePaymentPending);
  204. }
  205. }
  206. let amount_msat = partial_amount
  207. .is_none()
  208. .then(|| {
  209. melt_quote
  210. .msat_to_pay
  211. .map(|a| CLN_Amount::from_msat(a.into()))
  212. })
  213. .flatten();
  214. let mut cln_client = self.cln_client.lock().await;
  215. let cln_response = cln_client
  216. .call_typed(&PayRequest {
  217. bolt11: melt_quote.request.to_string(),
  218. amount_msat,
  219. label: None,
  220. riskfactor: None,
  221. maxfeepercent: None,
  222. retry_for: None,
  223. maxdelay: None,
  224. exemptfee: None,
  225. localinvreqid: None,
  226. exclude: None,
  227. maxfee: max_fee
  228. .map(|a| {
  229. let msat = to_unit(a, &melt_quote.unit, &CurrencyUnit::Msat)?;
  230. Ok::<CLN_Amount, Self::Err>(CLN_Amount::from_msat(msat.into()))
  231. })
  232. .transpose()?,
  233. description: None,
  234. partial_msat: partial_amount
  235. .map(|a| {
  236. let msat = to_unit(a, &melt_quote.unit, &CurrencyUnit::Msat)?;
  237. Ok::<cln_rpc::primitives::Amount, Self::Err>(CLN_Amount::from_msat(
  238. msat.into(),
  239. ))
  240. })
  241. .transpose()?,
  242. })
  243. .await;
  244. let response = match cln_response {
  245. Ok(pay_response) => {
  246. let status = match pay_response.status {
  247. PayStatus::COMPLETE => MeltQuoteState::Paid,
  248. PayStatus::PENDING => MeltQuoteState::Pending,
  249. PayStatus::FAILED => MeltQuoteState::Failed,
  250. };
  251. MakePaymentResponse {
  252. payment_proof: Some(hex::encode(pay_response.payment_preimage.to_vec())),
  253. payment_lookup_id: pay_response.payment_hash.to_string(),
  254. status,
  255. total_spent: to_unit(
  256. pay_response.amount_sent_msat.msat(),
  257. &CurrencyUnit::Msat,
  258. &melt_quote.unit,
  259. )?,
  260. unit: melt_quote.unit,
  261. }
  262. }
  263. Err(err) => {
  264. tracing::error!("Could not pay invoice: {}", err);
  265. return Err(Error::ClnRpc(err).into());
  266. }
  267. };
  268. Ok(response)
  269. }
  270. async fn create_incoming_payment_request(
  271. &self,
  272. amount: Amount,
  273. unit: &CurrencyUnit,
  274. description: String,
  275. unix_expiry: Option<u64>,
  276. ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
  277. let time_now = unix_time();
  278. let mut cln_client = self.cln_client.lock().await;
  279. let label = Uuid::new_v4().to_string();
  280. let amount = to_unit(amount, unit, &CurrencyUnit::Msat)?;
  281. let amount_msat = AmountOrAny::Amount(CLN_Amount::from_msat(amount.into()));
  282. let invoice_response = cln_client
  283. .call_typed(&InvoiceRequest {
  284. amount_msat,
  285. description,
  286. label: label.clone(),
  287. expiry: unix_expiry.map(|t| t - time_now),
  288. fallbacks: None,
  289. preimage: None,
  290. cltv: None,
  291. deschashonly: None,
  292. exposeprivatechannels: None,
  293. })
  294. .await
  295. .map_err(Error::from)?;
  296. let request = Bolt11Invoice::from_str(&invoice_response.bolt11)?;
  297. let expiry = request.expires_at().map(|t| t.as_secs());
  298. let payment_hash = request.payment_hash();
  299. Ok(CreateIncomingPaymentResponse {
  300. request_lookup_id: payment_hash.to_string(),
  301. request: request.to_string(),
  302. expiry,
  303. })
  304. }
  305. async fn check_incoming_payment_status(
  306. &self,
  307. payment_hash: &str,
  308. ) -> Result<MintQuoteState, Self::Err> {
  309. let mut cln_client = self.cln_client.lock().await;
  310. let listinvoices_response = cln_client
  311. .call_typed(&ListinvoicesRequest {
  312. payment_hash: Some(payment_hash.to_string()),
  313. label: None,
  314. invstring: None,
  315. offer_id: None,
  316. index: None,
  317. limit: None,
  318. start: None,
  319. })
  320. .await
  321. .map_err(Error::from)?;
  322. let status = match listinvoices_response.invoices.first() {
  323. Some(invoice_response) => cln_invoice_status_to_mint_state(invoice_response.status),
  324. None => {
  325. tracing::info!(
  326. "Check invoice called on unknown look up id: {}",
  327. payment_hash
  328. );
  329. return Err(Error::WrongClnResponse.into());
  330. }
  331. };
  332. Ok(status)
  333. }
  334. async fn check_outgoing_payment(
  335. &self,
  336. payment_hash: &str,
  337. ) -> Result<MakePaymentResponse, Self::Err> {
  338. let mut cln_client = self.cln_client.lock().await;
  339. let listpays_response = cln_client
  340. .call_typed(&ListpaysRequest {
  341. payment_hash: Some(payment_hash.parse().map_err(|_| Error::InvalidHash)?),
  342. bolt11: None,
  343. status: None,
  344. start: None,
  345. index: None,
  346. limit: None,
  347. })
  348. .await
  349. .map_err(Error::from)?;
  350. match listpays_response.pays.first() {
  351. Some(pays_response) => {
  352. let status = cln_pays_status_to_mint_state(pays_response.status);
  353. Ok(MakePaymentResponse {
  354. payment_lookup_id: pays_response.payment_hash.to_string(),
  355. payment_proof: pays_response.preimage.map(|p| hex::encode(p.to_vec())),
  356. status,
  357. total_spent: pays_response
  358. .amount_sent_msat
  359. .map_or(Amount::ZERO, |a| a.msat().into()),
  360. unit: CurrencyUnit::Msat,
  361. })
  362. }
  363. None => Ok(MakePaymentResponse {
  364. payment_lookup_id: payment_hash.to_string(),
  365. payment_proof: None,
  366. status: MeltQuoteState::Unknown,
  367. total_spent: Amount::ZERO,
  368. unit: CurrencyUnit::Msat,
  369. }),
  370. }
  371. }
  372. }
  373. impl Cln {
  374. /// Get last pay index for cln
  375. async fn get_last_pay_index(&self) -> Result<Option<u64>, Error> {
  376. let mut cln_client = self.cln_client.lock().await;
  377. let listinvoices_response = cln_client
  378. .call_typed(&ListinvoicesRequest {
  379. index: None,
  380. invstring: None,
  381. label: None,
  382. limit: None,
  383. offer_id: None,
  384. payment_hash: None,
  385. start: None,
  386. })
  387. .await
  388. .map_err(Error::from)?;
  389. match listinvoices_response.invoices.last() {
  390. Some(last_invoice) => Ok(last_invoice.pay_index),
  391. None => Ok(None),
  392. }
  393. }
  394. }
  395. fn cln_invoice_status_to_mint_state(status: ListinvoicesInvoicesStatus) -> MintQuoteState {
  396. match status {
  397. ListinvoicesInvoicesStatus::UNPAID => MintQuoteState::Unpaid,
  398. ListinvoicesInvoicesStatus::PAID => MintQuoteState::Paid,
  399. ListinvoicesInvoicesStatus::EXPIRED => MintQuoteState::Unpaid,
  400. }
  401. }
  402. fn cln_pays_status_to_mint_state(status: ListpaysPaysStatus) -> MeltQuoteState {
  403. match status {
  404. ListpaysPaysStatus::PENDING => MeltQuoteState::Pending,
  405. ListpaysPaysStatus::COMPLETE => MeltQuoteState::Paid,
  406. ListpaysPaysStatus::FAILED => MeltQuoteState::Failed,
  407. }
  408. }
  409. async fn fetch_invoice_by_payment_hash(
  410. cln_client: &mut cln_rpc::ClnRpc,
  411. payment_hash: &str,
  412. ) -> Result<Option<ListinvoicesInvoices>, Error> {
  413. match cln_client
  414. .call_typed(&ListinvoicesRequest {
  415. payment_hash: Some(payment_hash.to_string()),
  416. index: None,
  417. invstring: None,
  418. label: None,
  419. limit: None,
  420. offer_id: None,
  421. start: None,
  422. })
  423. .await
  424. {
  425. Ok(invoice_response) => Ok(invoice_response.invoices.first().cloned()),
  426. Err(e) => {
  427. tracing::warn!("Error fetching invoice: {e}");
  428. Err(Error::from(e))
  429. }
  430. }
  431. }