lib.rs 17 KB

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