lib.rs 17 KB

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