1use std::collections::HashMap;
2use std::fs::OpenOptions;
3use std::io::Write;
4use std::str::FromStr;
5use std::sync::Arc;
6use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
7
8use anyhow::{anyhow, Result};
9use bip39::*;
10use bitcoin::bip32::ChildNumber;
11use bitcoin::hashes::{sha256, Hash};
12use chrono::Local;
13use futures::{StreamExt, TryFutureExt};
14use log::{LevelFilter, Metadata, Record};
15use sdk_common::grpc;
16use sdk_common::prelude::*;
17use serde::Serialize;
18use serde_json::{json, Value};
19use strum_macros::EnumString;
20use tokio::sync::{mpsc, watch, Mutex};
21use tokio::time::{sleep, MissedTickBehavior};
22
23use crate::backup::{BackupRequest, BackupTransport, BackupWatcher};
24use crate::buy::{BuyBitcoinApi, BuyBitcoinService};
25use crate::chain::{
26 ChainService, Outspend, RecommendedFees, RedundantChainService, RedundantChainServiceTrait,
27 DEFAULT_MEMPOOL_SPACE_URL,
28};
29use crate::error::{
30 ConnectError, ReceiveOnchainError, ReceiveOnchainResult, ReceivePaymentError,
31 RedeemOnchainResult, SdkError, SdkResult, SendOnchainError, SendPaymentError,
32};
33use crate::lnurl::auth::SdkLnurlAuthSigner;
34use crate::lnurl::pay::*;
35use crate::lsp::LspInformation;
36use crate::models::{
37 sanitize::*, ChannelState, ClosedChannelPaymentDetails, Config, EnvironmentType, LspAPI,
38 NodeState, Payment, PaymentDetails, PaymentType, ReverseSwapPairInfo, ReverseSwapServiceAPI,
39 SwapInfo, SwapperAPI, INVOICE_PAYMENT_FEE_EXPIRY_SECONDS,
40};
41use crate::node_api::{CreateInvoiceRequest, NodeAPI};
42use crate::persist::cache::NodeStateStorage;
43use crate::persist::db::SqliteStorage;
44use crate::persist::swap::SwapStorage;
45use crate::persist::transactions::PaymentStorage;
46use crate::swap_in::{BTCReceiveSwap, BTCReceiveSwapParameters, TaprootSwapperAPI};
47use crate::swap_out::boltzswap::BoltzApi;
48use crate::swap_out::reverseswap::BTCSendSwap;
49use crate::*;
50
51pub type BreezServicesResult<T, E = ConnectError> = Result<T, E>;
52
53pub trait EventListener: Send + Sync {
55 fn on_event(&self, e: BreezEvent);
56}
57
58#[derive(Clone, Debug, PartialEq)]
61#[allow(clippy::large_enum_variant)]
62pub enum BreezEvent {
63 NewBlock { block: u32 },
65 InvoicePaid { details: InvoicePaidDetails },
67 Synced,
69 PaymentSucceed { details: Payment },
71 PaymentFailed { details: PaymentFailedData },
73 BackupStarted,
75 BackupSucceeded,
77 BackupFailed { details: BackupFailedData },
79 ReverseSwapUpdated { details: ReverseSwapInfo },
82 SwapUpdated { details: SwapInfo },
85}
86
87#[derive(Clone, Debug, PartialEq)]
88pub struct BackupFailedData {
89 pub error: String,
90}
91
92#[derive(Clone, Debug, PartialEq)]
93pub struct PaymentFailedData {
94 pub error: String,
95 pub node_id: String,
96 pub invoice: Option<LNInvoice>,
97 pub label: Option<String>,
98}
99
100#[derive(Clone, Debug, PartialEq)]
102pub struct InvoicePaidDetails {
103 pub payment_hash: String,
104 pub bolt11: String,
105 pub payment: Option<Payment>,
106}
107
108pub trait LogStream: Send + Sync {
109 fn log(&self, l: LogEntry);
110}
111
112#[derive(Clone, Debug, PartialEq)]
114pub struct SignMessageRequest {
115 pub message: String,
117}
118
119#[derive(Clone, Debug, PartialEq)]
121pub struct SignMessageResponse {
122 pub signature: String,
125}
126
127#[derive(Clone, Debug, PartialEq)]
129pub struct CheckMessageRequest {
130 pub message: String,
132 pub pubkey: String,
134 pub signature: String,
136}
137
138#[derive(Clone, Debug, PartialEq)]
140pub struct CheckMessageResponse {
141 pub is_valid: bool,
144}
145
146#[derive(Clone, PartialEq, EnumString, Serialize)]
147enum DevCommand {
148 #[strum(serialize = "generatediagnosticdata")]
150 GenerateDiagnosticData,
151}
152
153pub struct BreezServices {
155 config: Config,
156 started: Mutex<bool>,
157 node_api: Arc<dyn NodeAPI>,
158 lsp_api: Arc<dyn LspAPI>,
159 fiat_api: Arc<dyn FiatAPI>,
160 buy_bitcoin_api: Arc<dyn BuyBitcoinApi>,
161 support_api: Arc<dyn SupportAPI>,
162 chain_service: Arc<dyn ChainService>,
163 persister: Arc<SqliteStorage>,
164 rest_client: Arc<dyn RestClient>,
165 payment_receiver: Arc<dyn Receiver>,
166 btc_receive_swapper: Arc<BTCReceiveSwap>,
167 btc_send_swapper: Arc<BTCSendSwap>,
168 event_listener: Option<Box<dyn EventListener>>,
169 backup_watcher: Arc<BackupWatcher>,
170 shutdown_sender: watch::Sender<()>,
171}
172
173impl BreezServices {
174 pub async fn connect(
191 req: ConnectRequest,
192 event_listener: Box<dyn EventListener>,
193 ) -> BreezServicesResult<Arc<BreezServices>> {
194 let (sdk_version, sdk_git_hash) = Self::get_sdk_version();
195 info!("SDK v{sdk_version} ({sdk_git_hash})");
196 let start = Instant::now();
197 let services = BreezServicesBuilder::new(req.config)
198 .seed(req.seed)
199 .build(req.restore_only, Some(event_listener))
200 .await?;
201 services.start().await?;
202 let connect_duration = start.elapsed();
203 info!("SDK connected in: {connect_duration:?}");
204 Ok(services)
205 }
206
207 fn get_sdk_version() -> (&'static str, &'static str) {
208 let sdk_version = option_env!("CARGO_PKG_VERSION").unwrap_or_default();
209 let sdk_git_hash = option_env!("SDK_GIT_HASH").unwrap_or_default();
210 (sdk_version, sdk_git_hash)
211 }
212
213 async fn start(self: &Arc<BreezServices>) -> BreezServicesResult<()> {
221 let mut started = self.started.lock().await;
222 ensure_sdk!(
223 !*started,
224 ConnectError::Generic {
225 err: "BreezServices already started".into()
226 }
227 );
228
229 let start = Instant::now();
230 self.start_background_tasks().await?;
231 let start_duration = start.elapsed();
232 info!("SDK initialized in: {start_duration:?}");
233 *started = true;
234 Ok(())
235 }
236
237 pub async fn disconnect(&self) -> SdkResult<()> {
239 let mut started = self.started.lock().await;
240 ensure_sdk!(
241 *started,
242 SdkError::Generic {
243 err: "BreezServices is not running".into(),
244 }
245 );
246 self.shutdown_sender
247 .send(())
248 .map_err(|e| SdkError::Generic {
249 err: format!("Shutdown failed: {e}"),
250 })?;
251 self.shutdown_sender.closed().await;
252 *started = false;
253 Ok(())
254 }
255
256 pub async fn configure_node(&self, req: ConfigureNodeRequest) -> SdkResult<()> {
263 Ok(self.node_api.configure_node(req.close_to_address).await?)
264 }
265
266 pub async fn send_payment(
271 &self,
272 req: SendPaymentRequest,
273 ) -> Result<SendPaymentResponse, SendPaymentError> {
274 let parsed_invoice = parse_invoice(req.bolt11.as_str())?;
275 let invoice_expiration = parsed_invoice.timestamp + parsed_invoice.expiry;
276 let current_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
277 if invoice_expiration < current_time {
278 return Err(SendPaymentError::InvoiceExpired {
279 err: format!("Invoice expired at {invoice_expiration}"),
280 });
281 }
282 let invoice_amount_msat = parsed_invoice.amount_msat.unwrap_or_default();
283 let provided_amount_msat = req.amount_msat.unwrap_or_default();
284
285 validate_network(parsed_invoice.clone(), self.config.network)?;
287
288 let amount_msat = match (provided_amount_msat, invoice_amount_msat) {
289 (0, 0) => {
290 return Err(SendPaymentError::InvalidAmount {
291 err: "Amount must be provided when paying a zero invoice".into(),
292 })
293 }
294 (0, amount_msat) => amount_msat,
295 (amount_msat, 0) => amount_msat,
296 (_amount_1, _amount_2) => {
297 return Err(SendPaymentError::InvalidAmount {
298 err: "Amount should not be provided when paying a non zero invoice".into(),
299 })
300 }
301 };
302
303 if self
304 .persister
305 .get_completed_payment_by_hash(&parsed_invoice.payment_hash)?
306 .is_some()
307 {
308 return Err(SendPaymentError::AlreadyPaid);
309 }
310
311 let maybe_trampoline_id = self.get_trampoline_id(&req, &parsed_invoice)?;
315
316 self.persist_pending_payment(&parsed_invoice, amount_msat, req.label.clone())?;
317
318 let trampoline_result = if let Some(trampoline_id) = maybe_trampoline_id {
320 debug!("attempting trampoline payment");
321 match self
322 .node_api
323 .send_trampoline_payment(
324 parsed_invoice.bolt11.clone(),
325 amount_msat,
326 req.label.clone(),
327 trampoline_id,
328 )
329 .await
330 {
331 Ok(res) => Some(res),
332 Err(e) => {
333 if e.to_string().contains("missing balance") {
334 debug!("trampoline payment failed due to insufficient balance: {e:?}");
335 return Err(SendPaymentError::InsufficientBalance {
336 err: "Trampoline payment failed".into(),
337 });
338 }
339
340 warn!("trampoline payment failed: {e:?}");
341 None
342 }
343 }
344 } else {
345 debug!("not attempting trampoline payment");
346 None
347 };
348
349 let payment_res = match trampoline_result {
351 Some(res) => Ok(res),
352 None => {
353 debug!("attempting normal payment");
354 self.node_api
355 .send_payment(
356 parsed_invoice.bolt11.clone(),
357 req.amount_msat,
358 req.label.clone(),
359 )
360 .map_err(Into::into)
361 .await
362 }
363 };
364
365 debug!("payment returned {payment_res:?}");
366 let payment = self
367 .on_payment_completed(
368 parsed_invoice.payee_pubkey.clone(),
369 Some(parsed_invoice),
370 req.label,
371 payment_res,
372 )
373 .await?;
374 Ok(SendPaymentResponse { payment })
375 }
376
377 fn get_trampoline_id(
378 &self,
379 req: &SendPaymentRequest,
380 invoice: &LNInvoice,
381 ) -> Result<Option<Vec<u8>>, SendPaymentError> {
382 if !req.use_trampoline {
384 return Ok(None);
385 }
386
387 let lsp_pubkey = match self.persister.get_lsp_pubkey()? {
389 Some(lsp_pubkey) => lsp_pubkey,
390 None => return Ok(None),
391 };
392
393 if invoice.routing_hints.iter().any(|hint| {
396 hint.hops
397 .last()
398 .map(|hop| hop.src_node_id == lsp_pubkey)
399 .unwrap_or(false)
400 }) {
401 return Ok(None);
402 }
403
404 Ok(Some(hex::decode(lsp_pubkey).map_err(|_| {
406 SendPaymentError::Generic {
407 err: "failed to decode lsp pubkey".to_string(),
408 }
409 })?))
410 }
411
412 pub async fn send_spontaneous_payment(
414 &self,
415 req: SendSpontaneousPaymentRequest,
416 ) -> Result<SendPaymentResponse, SendPaymentError> {
417 let payment_res = self
418 .node_api
419 .send_spontaneous_payment(
420 req.node_id.clone(),
421 req.amount_msat,
422 req.extra_tlvs,
423 req.label.clone(),
424 )
425 .map_err(Into::into)
426 .await;
427 let payment = self
428 .on_payment_completed(req.node_id, None, req.label, payment_res)
429 .await?;
430 Ok(SendPaymentResponse { payment })
431 }
432
433 pub async fn lnurl_pay(&self, req: LnUrlPayRequest) -> Result<LnUrlPayResult, LnUrlPayError> {
442 match validate_lnurl_pay(
443 self.rest_client.as_ref(),
444 req.amount_msat,
445 &req.comment,
446 &req.data,
447 self.config.network,
448 req.validate_success_action_url,
449 )
450 .await?
451 {
452 ValidatedCallbackResponse::EndpointError { data: e } => {
453 Ok(LnUrlPayResult::EndpointError { data: e })
454 }
455 ValidatedCallbackResponse::EndpointSuccess { data: cb } => {
456 let pay_req = SendPaymentRequest {
457 bolt11: cb.pr.clone(),
458 amount_msat: None,
459 use_trampoline: req.use_trampoline,
460 label: req.payment_label,
461 };
462 let invoice = parse_invoice(cb.pr.as_str())?;
463
464 let payment = match self.send_payment(pay_req).await {
465 Ok(p) => Ok(p),
466 e @ Err(
467 SendPaymentError::InvalidInvoice { .. }
468 | SendPaymentError::ServiceConnectivity { .. },
469 ) => e,
470 Err(e) => {
471 return Ok(LnUrlPayResult::PayError {
472 data: LnUrlPayErrorData {
473 payment_hash: invoice.payment_hash,
474 reason: e.to_string(),
475 },
476 })
477 }
478 }?
479 .payment;
480 let details = match &payment.details {
481 PaymentDetails::ClosedChannel { .. } => {
482 return Err(LnUrlPayError::Generic {
483 err: "Payment lookup found unexpected payment type".into(),
484 });
485 }
486 PaymentDetails::Ln { data } => data,
487 };
488
489 let maybe_sa_processed: Option<SuccessActionProcessed> = match cb.success_action {
490 Some(sa) => {
491 let processed_sa = match sa {
492 SuccessAction::Aes { data } => {
494 let preimage = sha256::Hash::from_str(&details.payment_preimage)?;
495 let preimage_arr = preimage.as_byte_array();
496 let result = match (data, preimage_arr).try_into() {
497 Ok(data) => AesSuccessActionDataResult::Decrypted { data },
498 Err(e) => AesSuccessActionDataResult::ErrorStatus {
499 reason: e.to_string(),
500 },
501 };
502 SuccessActionProcessed::Aes { result }
503 }
504 SuccessAction::Message { data } => {
505 SuccessActionProcessed::Message { data }
506 }
507 SuccessAction::Url { data } => SuccessActionProcessed::Url { data },
508 };
509 Some(processed_sa)
510 }
511 None => None,
512 };
513
514 let lnurl_pay_domain = match req.data.ln_address {
515 Some(_) => None,
516 None => Some(req.data.domain),
517 };
518 self.persister.insert_payment_external_info(
520 &details.payment_hash,
521 PaymentExternalInfo {
522 lnurl_pay_success_action: maybe_sa_processed.clone(),
523 lnurl_pay_domain,
524 lnurl_pay_comment: req.comment,
525 lnurl_metadata: Some(req.data.metadata_str),
526 ln_address: req.data.ln_address,
527 lnurl_withdraw_endpoint: None,
528 attempted_amount_msat: invoice.amount_msat,
529 attempted_error: None,
530 },
531 )?;
532
533 Ok(LnUrlPayResult::EndpointSuccess {
534 data: lnurl::pay::LnUrlPaySuccessData {
535 payment,
536 success_action: maybe_sa_processed,
537 },
538 })
539 }
540 }
541 }
542
543 pub async fn lnurl_withdraw(
550 &self,
551 req: LnUrlWithdrawRequest,
552 ) -> Result<LnUrlWithdrawResult, LnUrlWithdrawError> {
553 let invoice = self
554 .receive_payment(ReceivePaymentRequest {
555 amount_msat: req.amount_msat,
556 description: req.description.unwrap_or_default(),
557 use_description_hash: Some(false),
558 ..Default::default()
559 })
560 .await?
561 .ln_invoice;
562
563 let lnurl_w_endpoint = req.data.callback.clone();
564 let res = validate_lnurl_withdraw(self.rest_client.as_ref(), req.data, invoice).await?;
565
566 if let LnUrlWithdrawResult::Ok { ref data } = res {
567 self.persister.insert_payment_external_info(
569 &data.invoice.payment_hash,
570 PaymentExternalInfo {
571 lnurl_pay_success_action: None,
572 lnurl_pay_domain: None,
573 lnurl_pay_comment: None,
574 lnurl_metadata: None,
575 ln_address: None,
576 lnurl_withdraw_endpoint: Some(lnurl_w_endpoint),
577 attempted_amount_msat: None,
578 attempted_error: None,
579 },
580 )?;
581 }
582
583 Ok(res)
584 }
585
586 pub async fn lnurl_auth(
592 &self,
593 req_data: LnUrlAuthRequestData,
594 ) -> Result<LnUrlCallbackStatus, LnUrlAuthError> {
595 Ok(perform_lnurl_auth(
596 self.rest_client.as_ref(),
597 &req_data,
598 &SdkLnurlAuthSigner::new(self.node_api.clone()),
599 )
600 .await?)
601 }
602
603 pub async fn receive_payment(
608 &self,
609 req: ReceivePaymentRequest,
610 ) -> Result<ReceivePaymentResponse, ReceivePaymentError> {
611 self.payment_receiver.receive_payment(req).await
612 }
613
614 pub async fn report_issue(&self, req: ReportIssueRequest) -> SdkResult<()> {
620 match self.persister.get_node_state()? {
621 Some(node_state) => match req {
622 ReportIssueRequest::PaymentFailure { data } => {
623 let payment = self
624 .persister
625 .get_payment_by_hash(&data.payment_hash)?
626 .ok_or(SdkError::Generic {
627 err: "Payment not found".into(),
628 })?;
629 let lsp_id = self.persister.get_lsp_id()?;
630
631 self.support_api
632 .report_payment_failure(node_state, payment, lsp_id, data.comment)
633 .await
634 }
635 },
636 None => Err(SdkError::Generic {
637 err: "Node state not found".into(),
638 }),
639 }
640 }
641
642 pub async fn node_credentials(&self) -> SdkResult<Option<NodeCredentials>> {
644 Ok(self.node_api.node_credentials().await?)
645 }
646
647 pub fn node_info(&self) -> SdkResult<NodeState> {
651 self.persister.get_node_state()?.ok_or(SdkError::Generic {
652 err: "Node info not found".into(),
653 })
654 }
655
656 pub async fn sign_message(&self, req: SignMessageRequest) -> SdkResult<SignMessageResponse> {
659 let signature = self.node_api.sign_message(&req.message).await?;
660 Ok(SignMessageResponse { signature })
661 }
662
663 pub async fn check_message(&self, req: CheckMessageRequest) -> SdkResult<CheckMessageResponse> {
666 let is_valid = self
667 .node_api
668 .check_message(&req.message, &req.pubkey, &req.signature)
669 .await?;
670 Ok(CheckMessageResponse { is_valid })
671 }
672
673 pub fn backup_status(&self) -> SdkResult<BackupStatus> {
675 let backup_time = self.persister.get_last_backup_time()?;
676 let sync_request = self.persister.get_last_sync_request()?;
677 Ok(BackupStatus {
678 last_backup_time: backup_time,
679 backed_up: sync_request.is_none(),
680 })
681 }
682
683 pub async fn backup(&self) -> SdkResult<()> {
685 let (on_complete, mut on_complete_receiver) = mpsc::channel::<Result<()>>(1);
686 let req = BackupRequest::with(on_complete, true);
687 self.backup_watcher.request_backup(req).await?;
688
689 match on_complete_receiver.recv().await {
690 Some(res) => res.map_err(|e| SdkError::Generic {
691 err: format!("Backup failed: {e}"),
692 }),
693 None => Err(SdkError::Generic {
694 err: "Backup process failed to complete".into(),
695 }),
696 }
697 }
698
699 pub async fn list_payments(&self, req: ListPaymentsRequest) -> SdkResult<Vec<Payment>> {
701 Ok(self.persister.list_payments(req)?)
702 }
703
704 pub async fn payment_by_hash(&self, hash: String) -> SdkResult<Option<Payment>> {
706 Ok(self.persister.get_payment_by_hash(&hash)?)
707 }
708
709 pub async fn set_payment_metadata(&self, hash: String, metadata: String) -> SdkResult<()> {
711 Ok(self
712 .persister
713 .set_payment_external_metadata(hash, metadata)?)
714 }
715
716 pub async fn redeem_onchain_funds(
718 &self,
719 req: RedeemOnchainFundsRequest,
720 ) -> RedeemOnchainResult<RedeemOnchainFundsResponse> {
721 let txid = self
722 .node_api
723 .redeem_onchain_funds(req.to_address, req.sat_per_vbyte)
724 .await?;
725 self.sync().await?;
726 Ok(RedeemOnchainFundsResponse { txid })
727 }
728
729 pub async fn prepare_redeem_onchain_funds(
730 &self,
731 req: PrepareRedeemOnchainFundsRequest,
732 ) -> RedeemOnchainResult<PrepareRedeemOnchainFundsResponse> {
733 let response = self.node_api.prepare_redeem_onchain_funds(req).await?;
734 Ok(response)
735 }
736
737 pub async fn fetch_fiat_rates(&self) -> SdkResult<Vec<Rate>> {
739 self.fiat_api.fetch_fiat_rates().await.map_err(Into::into)
740 }
741
742 pub async fn list_fiat_currencies(&self) -> SdkResult<Vec<FiatCurrency>> {
745 self.fiat_api
746 .list_fiat_currencies()
747 .await
748 .map_err(Into::into)
749 }
750
751 pub async fn list_lsps(&self) -> SdkResult<Vec<LspInformation>> {
753 self.lsp_api.list_lsps(self.node_info()?.id).await
754 }
755
756 pub async fn connect_lsp(&self, lsp_id: String) -> SdkResult<()> {
758 let lsp_pubkey = match self.list_lsps().await?.iter().find(|lsp| lsp.id == lsp_id) {
759 Some(lsp) => lsp.pubkey.clone(),
760 None => {
761 return Err(SdkError::Generic {
762 err: format!("Unknown LSP: {lsp_id}"),
763 })
764 }
765 };
766
767 self.persister.set_lsp(lsp_id, Some(lsp_pubkey))?;
768 self.sync().await?;
769 if let Some(webhook_url) = self.persister.get_webhook_url()? {
770 self.register_payment_notifications(webhook_url).await?
771 }
772 Ok(())
773 }
774
775 pub async fn lsp_id(&self) -> SdkResult<Option<String>> {
777 Ok(self.persister.get_lsp_id()?)
778 }
779
780 pub async fn fetch_lsp_info(&self, id: String) -> SdkResult<Option<LspInformation>> {
782 get_lsp_by_id(self.persister.clone(), self.lsp_api.clone(), id.as_str()).await
783 }
784
785 pub async fn open_channel_fee(
788 &self,
789 req: OpenChannelFeeRequest,
790 ) -> SdkResult<OpenChannelFeeResponse> {
791 let lsp_info = self.lsp_info().await?;
792 let fee_params = lsp_info
793 .cheapest_open_channel_fee(req.expiry.unwrap_or(INVOICE_PAYMENT_FEE_EXPIRY_SECONDS))?
794 .clone();
795
796 let node_state = self.node_info()?;
797 let fee_msat = req.amount_msat.map(|req_amount_msat| {
798 match node_state.max_receivable_single_payment_amount_msat >= req_amount_msat {
799 true => 0,
801 false => fee_params.get_channel_fees_msat_for(req_amount_msat),
803 }
804 });
805
806 Ok(OpenChannelFeeResponse {
807 fee_msat,
808 fee_params,
809 })
810 }
811
812 pub async fn close_lsp_channels(&self) -> SdkResult<Vec<String>> {
816 let lsps = self.get_lsps().await?;
817 let mut all_txids = Vec::new();
818 for lsp in lsps {
819 let txids = self.node_api.close_peer_channels(lsp.pubkey).await?;
820 all_txids.extend(txids);
821 }
822 self.sync().await?;
823 Ok(all_txids)
824 }
825
826 pub async fn receive_onchain(
839 &self,
840 req: ReceiveOnchainRequest,
841 ) -> ReceiveOnchainResult<SwapInfo> {
842 if let Some(in_progress) = self.in_progress_swap().await? {
843 return Err(ReceiveOnchainError::SwapInProgress{ err:format!(
844 "A swap was detected for address {}. Use in_progress_swap method to get the current swap state",
845 in_progress.bitcoin_address
846 )});
847 }
848 let channel_opening_fees = req.opening_fee_params.unwrap_or(
849 self.lsp_info()
850 .await?
851 .cheapest_open_channel_fee(SWAP_PAYMENT_FEE_EXPIRY_SECONDS)?
852 .clone(),
853 );
854
855 let swap_info = self
856 .btc_receive_swapper
857 .create_swap(channel_opening_fees)
858 .await?;
859 if let Some(webhook_url) = self.persister.get_webhook_url()? {
860 let address = &swap_info.bitcoin_address;
861 info!("Registering for onchain tx notification for address {address}");
862 self.register_onchain_tx_notification(address, &webhook_url)
863 .await?;
864 }
865 Ok(swap_info)
866 }
867
868 pub async fn in_progress_swap(&self) -> SdkResult<Option<SwapInfo>> {
871 let tip = self.chain_service.current_tip().await?;
872 self.btc_receive_swapper.rescan_monitored_swaps(tip).await?;
873 let in_progress = self.btc_receive_swapper.list_in_progress_swaps()?;
874 if !in_progress.is_empty() {
875 return Ok(Some(in_progress[0].clone()));
876 }
877 Ok(None)
878 }
879
880 pub async fn rescan_swaps(&self) -> SdkResult<()> {
883 let tip = self.chain_service.current_tip().await?;
884 self.btc_receive_swapper.rescan_swaps(tip).await?;
885 Ok(())
886 }
887
888 pub async fn redeem_swap(&self, swap_address: String) -> SdkResult<()> {
895 let tip = self.chain_service.current_tip().await?;
896 self.btc_receive_swapper
897 .rescan_swap(&swap_address, tip)
898 .await?;
899 self.btc_receive_swapper.redeem_swap(swap_address).await?;
900 Ok(())
901 }
902
903 pub async fn list_swaps(&self, req: ListSwapsRequest) -> SdkResult<Vec<SwapInfo>> {
907 Ok(self.persister.list_swaps(req)?)
908 }
909
910 pub async fn claim_reverse_swap(&self, lockup_address: String) -> SdkResult<()> {
917 Ok(self
918 .btc_send_swapper
919 .claim_reverse_swap(lockup_address)
920 .await?)
921 }
922
923 pub async fn fetch_reverse_swap_fees(
938 &self,
939 req: ReverseSwapFeesRequest,
940 ) -> SdkResult<ReverseSwapPairInfo> {
941 let mut res = self.btc_send_swapper.fetch_reverse_swap_fees().await?;
942
943 if let Some(amt) = req.send_amount_sat {
944 ensure_sdk!(amt <= res.max, SdkError::generic("Send amount is too high"));
945 ensure_sdk!(amt >= res.min, SdkError::generic("Send amount is too low"));
946
947 if let Some(claim_tx_feerate) = req.claim_tx_feerate {
948 res.fees_claim = BTCSendSwap::calculate_claim_tx_fee(claim_tx_feerate)?;
949 }
950
951 let service_fee_sat = swap_out::get_service_fee_sat(amt, res.fees_percentage);
952 res.total_fees = Some(service_fee_sat + res.fees_lockup + res.fees_claim);
953 }
954
955 Ok(res)
956 }
957 async fn max_reverse_swap_amount(&self) -> SdkResult<u64> {
963 let last_hop = self.btc_send_swapper.last_hop_for_payment().await?;
965 info!("max_reverse_swap_amount last_hop={last_hop:?}");
966 let max_to_pay = self
970 .node_api
971 .max_sendable_amount(
972 Some(
973 hex::decode(&last_hop.src_node_id).map_err(|e| SdkError::Generic {
974 err: format!("Failed to decode hex node_id: {e}"),
975 })?,
976 ),
977 swap_out::reverseswap::MAX_PAYMENT_PATH_HOPS,
978 Some(&last_hop),
979 )
980 .await?;
981
982 let total_msat: u64 = max_to_pay.into_iter().map(|m| m.amount_msat).sum();
984 let total_sat = total_msat / 1000;
985 Ok(total_sat)
986 }
987
988 pub async fn list_refundables(&self) -> SdkResult<Vec<SwapInfo>> {
990 Ok(self.btc_receive_swapper.list_refundables()?)
991 }
992
993 pub async fn prepare_refund(
998 &self,
999 req: PrepareRefundRequest,
1000 ) -> SdkResult<PrepareRefundResponse> {
1001 Ok(self.btc_receive_swapper.prepare_refund(req).await?)
1002 }
1003
1004 pub async fn refund(&self, req: RefundRequest) -> SdkResult<RefundResponse> {
1008 Ok(self.btc_receive_swapper.refund(req).await?)
1009 }
1010
1011 pub async fn onchain_payment_limits(&self) -> SdkResult<OnchainPaymentLimitsResponse> {
1012 let fee_info = self.btc_send_swapper.fetch_reverse_swap_fees().await?;
1013 debug!("Reverse swap pair info: {fee_info:?}");
1014 let max_amt_current_channels = self.max_reverse_swap_amount().await?;
1015 debug!("Max send amount possible with current channels: {max_amt_current_channels:?}");
1016
1017 Ok(OnchainPaymentLimitsResponse {
1018 min_sat: fee_info.min,
1019 max_sat: fee_info.max,
1020 max_payable_sat: max_amt_current_channels,
1021 })
1022 }
1023
1024 pub async fn prepare_onchain_payment(
1032 &self,
1033 req: PrepareOnchainPaymentRequest,
1034 ) -> Result<PrepareOnchainPaymentResponse, SendOnchainError> {
1035 let fees_claim = BTCSendSwap::calculate_claim_tx_fee(req.claim_tx_feerate)?;
1036 BTCSendSwap::validate_claim_tx_fee(fees_claim)?;
1037
1038 let fee_info = self.btc_send_swapper.fetch_reverse_swap_fees().await?;
1039
1040 let fees_lockup = fee_info.fees_lockup;
1042 let p = fee_info.fees_percentage;
1043 let (send_amt, recv_amt) = match req.amount_type {
1044 SwapAmountType::Send => {
1045 let temp_send_amt = req.amount_sat;
1046 let service_fees = swap_out::get_service_fee_sat(temp_send_amt, p);
1047 let total_fees = service_fees + fees_lockup + fees_claim;
1048 ensure_sdk!(
1049 temp_send_amt > total_fees,
1050 SendOnchainError::generic(
1051 "Send amount is not high enough to account for all fees"
1052 )
1053 );
1054
1055 (temp_send_amt, temp_send_amt - total_fees)
1056 }
1057 SwapAmountType::Receive => {
1058 let temp_recv_amt = req.amount_sat;
1059 let send_amt_minus_service_fee = temp_recv_amt + fees_lockup + fees_claim;
1060 let temp_send_amt = swap_out::get_invoice_amount_sat(send_amt_minus_service_fee, p);
1061
1062 (temp_send_amt, temp_recv_amt)
1063 }
1064 };
1065
1066 let is_send_in_range = send_amt >= fee_info.min && send_amt <= fee_info.max;
1067 ensure_sdk!(is_send_in_range, SendOnchainError::OutOfRange);
1068
1069 Ok(PrepareOnchainPaymentResponse {
1070 fees_hash: fee_info.fees_hash.clone(),
1071 fees_percentage: p,
1072 fees_lockup,
1073 fees_claim,
1074 sender_amount_sat: send_amt,
1075 recipient_amount_sat: recv_amt,
1076 total_fees: send_amt - recv_amt,
1077 })
1078 }
1079
1080 pub async fn pay_onchain(
1082 &self,
1083 req: PayOnchainRequest,
1084 ) -> Result<PayOnchainResponse, SendOnchainError> {
1085 ensure_sdk!(
1086 req.prepare_res.sender_amount_sat > req.prepare_res.recipient_amount_sat,
1087 SendOnchainError::generic("Send amount must be bigger than receive amount")
1088 );
1089
1090 ensure_sdk!(self.in_progress_onchain_payments().await?.is_empty(), SendOnchainError::Generic { err:
1091 "You can only start a new one after after the ongoing ones finish. \
1092 Use the in_progress_onchain_payments method to get an overview of currently ongoing reverse swaps".into(),
1093 });
1094
1095 let full_rsi = self.btc_send_swapper.create_reverse_swap(req).await?;
1096 let reverse_swap_info = self
1097 .btc_send_swapper
1098 .convert_reverse_swap_info(full_rsi.clone())
1099 .await?;
1100 self.do_sync(false).await?;
1101
1102 if let Some(webhook_url) = self.persister.get_webhook_url()? {
1103 let address = &full_rsi
1104 .get_lockup_address(self.config.network)?
1105 .to_string();
1106 info!("Registering for onchain tx notification for address {address}");
1107 self.register_onchain_tx_notification(address, &webhook_url)
1108 .await?;
1109 }
1110 Ok(PayOnchainResponse { reverse_swap_info })
1111 }
1112
1113 pub async fn in_progress_onchain_payments(&self) -> SdkResult<Vec<ReverseSwapInfo>> {
1115 let full_rsis = self.btc_send_swapper.list_blocking().await?;
1116
1117 let mut rsis = vec![];
1118 for full_rsi in full_rsis {
1119 let rsi = self
1120 .btc_send_swapper
1121 .convert_reverse_swap_info(full_rsi)
1122 .await?;
1123 rsis.push(rsi);
1124 }
1125
1126 Ok(rsis)
1127 }
1128
1129 pub async fn execute_dev_command(&self, command: String) -> SdkResult<String> {
1132 let dev_cmd_res = DevCommand::from_str(&command);
1133
1134 match dev_cmd_res {
1135 Ok(dev_cmd) => match dev_cmd {
1136 DevCommand::GenerateDiagnosticData => self.generate_diagnostic_data().await,
1137 },
1138 Err(_) => Ok(crate::serializer::to_string_pretty(
1139 &self.node_api.execute_command(command).await?,
1140 )?),
1141 }
1142 }
1143
1144 pub async fn generate_diagnostic_data(&self) -> SdkResult<String> {
1147 let now_sec = SystemTime::now()
1148 .duration_since(UNIX_EPOCH)
1149 .map(|d| d.as_secs())
1150 .unwrap_or_default();
1151 let node_data = self
1152 .node_api
1153 .generate_diagnostic_data()
1154 .await
1155 .unwrap_or_else(|e| json!({"error": e.to_string()}));
1156 let sdk_data = self
1157 .generate_sdk_diagnostic_data()
1158 .await
1159 .unwrap_or_else(|e| json!({"error": e.to_string()}));
1160 let result = json!({
1161 "timestamp": now_sec,
1162 "node": node_data,
1163 "sdk": sdk_data
1164 });
1165 Ok(crate::serializer::to_string_pretty(&result)?)
1166 }
1167
1168 pub async fn sync(&self) -> SdkResult<()> {
1174 Ok(self.do_sync(false).await?)
1175 }
1176
1177 async fn do_sync(&self, match_local_balance: bool) -> Result<()> {
1178 let start = Instant::now();
1179 let node_pubkey = self.node_api.node_id().await?;
1180 self.connect_lsp_peer(node_pubkey).await?;
1181
1182 let sync_state = self.persister.get_sync_state()?;
1184 let new_data = &self
1185 .node_api
1186 .pull_changed(sync_state.clone(), match_local_balance)
1187 .await?;
1188
1189 debug!(
1190 "pull changed old state={:?} new state={:?}",
1191 sync_state, new_data.sync_state
1192 );
1193
1194 self.persister.set_node_state(&new_data.node_state)?;
1196
1197 let channels_before_update = self.persister.list_channels()?;
1198 self.persister.update_channels(&new_data.channels)?;
1199 let channels_after_update = self.persister.list_channels()?;
1200
1201 if channels_before_update.len() != channels_after_update.len() {
1203 info!("fetching static backup file from node");
1204 let backup = self.node_api.static_backup().await?;
1205 self.persister.set_static_backup(backup)?;
1206 }
1207
1208 let mut closed_channel_payments: Vec<Payment> = vec![];
1210 for closed_channel in
1211 self.persister.list_channels()?.into_iter().filter(|c| {
1212 c.state == ChannelState::Closed || c.state == ChannelState::PendingClose
1213 })
1214 {
1215 let closed_channel_tx = self.closed_channel_to_transaction(closed_channel).await?;
1216 closed_channel_payments.push(closed_channel_tx);
1217 }
1218
1219 let mut payments = closed_channel_payments;
1221 payments.extend(new_data.payments.clone());
1222 self.persister.delete_pseudo_payments()?;
1223 self.persister.insert_or_update_payments(&payments, false)?;
1224 let duration = start.elapsed();
1225 info!("Sync duration: {duration:?}");
1226
1227 self.persister.set_sync_state(&new_data.sync_state)?;
1229 self.notify_event_listeners(BreezEvent::Synced).await?;
1230 Ok(())
1231 }
1232
1233 async fn connect_lsp_peer(&self, node_pubkey: String) -> SdkResult<()> {
1237 let lsps = self.lsp_api.list_lsps(node_pubkey).await?;
1238 let lsp = match self
1239 .persister
1240 .get_lsp_id()?
1241 .and_then(|lsp_id| lsps.iter().find(|lsp| lsp.id == lsp_id))
1242 .or_else(|| lsps.first())
1243 {
1244 Some(lsp) => lsp.clone(),
1245 None => return Ok(()),
1246 };
1247
1248 self.persister.set_lsp(lsp.id, Some(lsp.pubkey.clone()))?;
1249 let node_state = match self.node_info() {
1250 Ok(node_state) => node_state,
1251 Err(_) => return Ok(()),
1252 };
1253
1254 let node_id = lsp.pubkey;
1255 let address = lsp.host;
1256 let lsp_connected = node_state
1257 .connected_peers
1258 .iter()
1259 .any(|e| e == node_id.as_str());
1260 if !lsp_connected {
1261 debug!("connecting to lsp {}@{}", node_id.clone(), address.clone());
1262 self.node_api
1263 .connect_peer(node_id.clone(), address.clone())
1264 .await
1265 .map_err(|e| SdkError::ServiceConnectivity {
1266 err: format!("(LSP: {node_id}) Failed to connect: {e}"),
1267 })?;
1268 debug!("connected to lsp {node_id}@{address}");
1269 }
1270
1271 Ok(())
1272 }
1273
1274 fn persist_pending_payment(
1275 &self,
1276 invoice: &LNInvoice,
1277 amount_msat: u64,
1278 label: Option<String>,
1279 ) -> Result<(), SendPaymentError> {
1280 self.persister.insert_or_update_payments(
1281 &[Payment {
1282 id: invoice.payment_hash.clone(),
1283 payment_type: PaymentType::Sent,
1284 payment_time: SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64,
1285 amount_msat,
1286 fee_msat: 0,
1287 status: PaymentStatus::Pending,
1288 error: None,
1289 description: invoice.description.clone(),
1290 details: PaymentDetails::Ln {
1291 data: LnPaymentDetails {
1292 payment_hash: invoice.payment_hash.clone(),
1293 label: label.unwrap_or_default(),
1294 destination_pubkey: invoice.payee_pubkey.clone(),
1295 payment_preimage: String::new(),
1296 keysend: false,
1297 bolt11: invoice.bolt11.clone(),
1298 lnurl_success_action: None,
1299 lnurl_pay_domain: None,
1300 lnurl_pay_comment: None,
1301 ln_address: None,
1302 lnurl_metadata: None,
1303 lnurl_withdraw_endpoint: None,
1304 swap_info: None,
1305 reverse_swap_info: None,
1306 pending_expiration_block: None,
1307 open_channel_bolt11: None,
1308 },
1309 },
1310 metadata: None,
1311 }],
1312 true,
1313 )?;
1314
1315 self.persister.insert_payment_external_info(
1316 &invoice.payment_hash,
1317 PaymentExternalInfo {
1318 lnurl_pay_success_action: None,
1319 lnurl_pay_domain: None,
1320 lnurl_pay_comment: None,
1321 lnurl_metadata: None,
1322 ln_address: None,
1323 lnurl_withdraw_endpoint: None,
1324 attempted_amount_msat: invoice.amount_msat.map_or(Some(amount_msat), |_| None),
1325 attempted_error: None,
1326 },
1327 )?;
1328 Ok(())
1329 }
1330
1331 async fn on_payment_completed(
1332 &self,
1333 node_id: String,
1334 invoice: Option<LNInvoice>,
1335 label: Option<String>,
1336 payment_res: Result<Payment, SendPaymentError>,
1337 ) -> Result<Payment, SendPaymentError> {
1338 self.do_sync(false).await?;
1339 match payment_res {
1340 Ok(payment) => {
1341 self.notify_event_listeners(BreezEvent::PaymentSucceed {
1342 details: payment.clone(),
1343 })
1344 .await?;
1345 Ok(payment)
1346 }
1347 Err(e) => {
1348 if let Some(invoice) = invoice.clone() {
1349 self.persister.update_payment_attempted_error(
1350 &invoice.payment_hash,
1351 Some(e.to_string()),
1352 )?;
1353 }
1354 self.notify_event_listeners(BreezEvent::PaymentFailed {
1355 details: PaymentFailedData {
1356 error: e.to_string(),
1357 node_id,
1358 invoice,
1359 label,
1360 },
1361 })
1362 .await?;
1363 Err(e)
1364 }
1365 }
1366 }
1367
1368 async fn on_event(&self, e: BreezEvent) -> Result<()> {
1369 debug!("breez services got event {e:?}");
1370 self.notify_event_listeners(e.clone()).await
1371 }
1372
1373 async fn notify_event_listeners(&self, e: BreezEvent) -> Result<()> {
1374 if let Err(err) = self.btc_receive_swapper.on_event(e.clone()).await {
1375 debug!("btc_receive_swapper failed to process event {e:?}: {err:?}")
1376 };
1377 if let Err(err) = self.btc_send_swapper.on_event(e.clone()).await {
1378 debug!("btc_send_swapper failed to process event {e:?}: {err:?}")
1379 };
1380
1381 if self.event_listener.is_some() {
1382 self.event_listener.as_ref().unwrap().on_event(e.clone())
1383 }
1384 Ok(())
1385 }
1386
1387 pub async fn lsp_info(&self) -> SdkResult<LspInformation> {
1389 get_lsp(self.persister.clone(), self.lsp_api.clone()).await
1390 }
1391
1392 async fn get_lsps(&self) -> SdkResult<Vec<LspInformation>> {
1393 get_lsps(self.persister.clone(), self.lsp_api.clone()).await
1394 }
1395
1396 pub async fn recommended_fees(&self) -> SdkResult<RecommendedFees> {
1398 self.chain_service.recommended_fees().await
1399 }
1400
1401 pub fn default_config(
1403 env_type: EnvironmentType,
1404 api_key: String,
1405 node_config: NodeConfig,
1406 ) -> Config {
1407 match env_type {
1408 EnvironmentType::Production => Config::production(api_key, node_config),
1409 EnvironmentType::Staging => Config::staging(api_key, node_config),
1410 EnvironmentType::Regtest => Config::regtest(api_key, node_config),
1411 }
1412 }
1413
1414 pub fn static_backup(req: StaticBackupRequest) -> SdkResult<StaticBackupResponse> {
1418 let storage = SqliteStorage::new(req.working_dir);
1419 Ok(StaticBackupResponse {
1420 backup: storage.get_static_backup()?,
1421 })
1422 }
1423
1424 pub async fn service_health_check(api_key: String) -> SdkResult<ServiceHealthCheckResponse> {
1426 let support_api: Arc<dyn SupportAPI> = Arc::new(BreezServer::new(
1427 PRODUCTION_BREEZSERVER_URL.to_string(),
1428 Some(api_key),
1429 )?);
1430
1431 support_api.service_health_check().await
1432 }
1433
1434 pub async fn buy_bitcoin(
1439 &self,
1440 req: BuyBitcoinRequest,
1441 ) -> Result<BuyBitcoinResponse, ReceiveOnchainError> {
1442 let swap_info = self
1443 .receive_onchain(ReceiveOnchainRequest {
1444 opening_fee_params: req.opening_fee_params,
1445 })
1446 .await?;
1447 let url = self
1448 .buy_bitcoin_api
1449 .buy_bitcoin(req.provider, &swap_info, req.redirect_url)
1450 .await?;
1451
1452 Ok(BuyBitcoinResponse {
1453 url,
1454 opening_fee_params: swap_info.channel_opening_fees,
1455 })
1456 }
1457
1458 async fn start_background_tasks(self: &Arc<BreezServices>) -> SdkResult<()> {
1462 let (shutdown_signer_sender, signer_signer_receiver) = watch::channel(());
1464 self.start_signer(signer_signer_receiver).await;
1465 self.start_node_keep_alive(self.shutdown_sender.subscribe())
1466 .await;
1467
1468 match self.persister.get_node_state()? {
1470 Some(node) => {
1471 info!("Starting existing node {}", node.id);
1472 self.connect_lsp_peer(node.id).await?;
1473 }
1474 None => {
1475 info!("First run, syncing in foreground");
1477 self.sync().await?;
1478 info!("First run, finished running syncing in foreground");
1479 }
1480 }
1481
1482 self.start_backup_watcher().await?;
1484
1485 self.track_backup_events().await;
1487
1488 self.track_swap_events().await;
1490
1491 self.track_invoices().await;
1493
1494 self.track_new_blocks().await;
1496
1497 self.track_logs().await;
1499
1500 let mut shutdown_receiver = self.shutdown_sender.subscribe();
1502 tokio::spawn(async move {
1503 _ = shutdown_receiver.changed().await;
1504 debug!("Received the signal to exit signer");
1505 _ = shutdown_signer_sender.send(());
1506 shutdown_signer_sender.closed().await;
1507 });
1508
1509 self.init_chainservice_urls().await?;
1510
1511 Ok(())
1512 }
1513
1514 async fn start_signer(self: &Arc<BreezServices>, mut shutdown_receiver: watch::Receiver<()>) {
1515 let node_api = self.node_api.clone();
1516
1517 tokio::spawn(async move {
1518 loop {
1519 let (tx, rx) = mpsc::channel(1);
1520 let mut node_future = node_api.start(rx);
1521 tokio::select! {
1522 _ = &mut node_future => {
1523 warn!("Node exited itself, restarting");
1524 tokio::time::sleep(Duration::from_secs(1)).await;
1525 }
1526 _ = shutdown_receiver.changed() => {
1527 debug!("Shutting down node");
1528 drop(tx);
1529 debug!("Waiting for node to shut down");
1530 node_future.await;
1531 return;
1532 }
1533 };
1534 }
1535 });
1536 }
1537
1538 async fn start_node_keep_alive(
1539 self: &Arc<BreezServices>,
1540 shutdown_receiver: watch::Receiver<()>,
1541 ) {
1542 let cloned = self.clone();
1543 tokio::spawn(async move {
1544 cloned.node_api.start_keep_alive(shutdown_receiver).await;
1545 });
1546 }
1547
1548 async fn start_backup_watcher(self: &Arc<BreezServices>) -> Result<()> {
1549 self.backup_watcher
1550 .start(self.shutdown_sender.subscribe())
1551 .await
1552 .map_err(|e| anyhow!("Failed to start backup watcher: {e}"))?;
1553
1554 let force_backup = self
1556 .persister
1557 .get_last_sync_version()
1558 .map_err(|e| anyhow!("Failed to read last sync version: {e}"))?
1559 .is_none();
1560 self.backup_watcher
1561 .request_backup(BackupRequest::new(force_backup))
1562 .await
1563 .map_err(|e| anyhow!("Failed to request backup: {e}"))
1564 }
1565
1566 async fn track_backup_events(self: &Arc<BreezServices>) {
1567 let cloned = self.clone();
1568 tokio::spawn(async move {
1569 let mut events_stream = cloned.backup_watcher.subscribe_events();
1570 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1571 loop {
1572 tokio::select! {
1573 backup_event = events_stream.recv() => {
1574 if let Ok(e) = backup_event {
1575 if let Err(err) = cloned.notify_event_listeners(e).await {
1576 error!("error handling backup event: {err:?}");
1577 }
1578 }
1579 let backup_status = cloned.backup_status();
1580 info!("backup status: {backup_status:?}");
1581 },
1582 _ = shutdown_receiver.changed() => {
1583 debug!("Backup watcher task completed");
1584 break;
1585 }
1586 }
1587 }
1588 });
1589 }
1590
1591 async fn track_swap_events(self: &Arc<BreezServices>) {
1592 let cloned = self.clone();
1593 tokio::spawn(async move {
1594 let mut swap_events_stream = cloned.btc_receive_swapper.subscribe_status_changes();
1595 let mut rev_swap_events_stream = cloned.btc_send_swapper.subscribe_status_changes();
1596 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1597 loop {
1598 tokio::select! {
1599 swap_event = swap_events_stream.recv() => {
1600 if let Ok(e) = swap_event {
1601 if let Err(err) = cloned.notify_event_listeners(e).await {
1602 error!("error handling swap event: {err:?}");
1603 }
1604 }
1605 },
1606 rev_swap_event = rev_swap_events_stream.recv() => {
1607 if let Ok(e) = rev_swap_event {
1608 if let Err(err) = cloned.notify_event_listeners(e).await {
1609 error!("error handling reverse swap event: {err:?}");
1610 }
1611 }
1612 },
1613 _ = shutdown_receiver.changed() => {
1614 debug!("Swap events handling task completed");
1615 break;
1616 }
1617 }
1618 }
1619 });
1620 }
1621
1622 async fn track_invoices(self: &Arc<BreezServices>) {
1623 let cloned = self.clone();
1624 tokio::spawn(async move {
1625 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1626 loop {
1627 if shutdown_receiver.has_changed().unwrap_or(true) {
1628 return;
1629 }
1630 let mut invoice_stream = match cloned.node_api.stream_incoming_payments().await {
1631 Ok(invoice_stream) => invoice_stream,
1632 Err(e) => {
1633 warn!("stream incoming payments returned error: {e:?}");
1634 tokio::select! {
1635 _ = sleep(Duration::from_secs(1)) => {
1636 continue
1637 }
1638 _ = shutdown_receiver.changed() => {
1639 debug!("Invoice tracking task has completed");
1640 return;
1641 }
1642 };
1643 }
1644 };
1645
1646 loop {
1647 let paid_invoice_res = tokio::select! {
1648 paid_invoice_res = invoice_stream.next() => paid_invoice_res,
1649 _ = shutdown_receiver.changed() => {
1650 debug!("Invoice tracking task has completed");
1651 return;
1652 }
1653 };
1654
1655 let p = match paid_invoice_res {
1656 Some(p) => p,
1657 None => {
1658 debug!("invoice stream got None");
1659 break;
1660 }
1661 };
1662
1663 debug!("invoice stream got new invoice");
1664 let mut payment: Option<crate::models::Payment> = p.clone().try_into().ok();
1665 if let Some(ref p) = payment {
1666 let res = cloned
1667 .persister
1668 .insert_or_update_payments(&vec![p.clone()], false);
1669 debug!("paid invoice was added to payments list {res:?}");
1670 if let Ok(Some(mut node_info)) = cloned.persister.get_node_state() {
1671 node_info.channels_balance_msat += p.amount_msat;
1672 let res = cloned.persister.set_node_state(&node_info);
1673 debug!("channel balance was updated {res:?}");
1674 }
1675 payment = cloned
1676 .persister
1677 .get_payment_by_hash(&p.id)
1678 .unwrap_or(payment);
1679 }
1680 _ = cloned
1681 .on_event(BreezEvent::InvoicePaid {
1682 details: InvoicePaidDetails {
1683 payment_hash: hex::encode(p.payment_hash),
1684 bolt11: p.bolt11,
1685 payment,
1686 },
1687 })
1688 .await;
1689 if let Err(e) = cloned.do_sync(true).await {
1690 error!("failed to sync after paid invoice: {e:?}");
1691 }
1692 }
1693
1694 tokio::select! {
1695 _ = sleep(Duration::from_secs(1)) => {
1696 continue
1697 }
1698 _ = shutdown_receiver.changed() => {
1699 debug!("Invoice tracking task has completed");
1700 return;
1701 }
1702 };
1703 }
1704 });
1705 }
1706
1707 async fn track_logs(self: &Arc<BreezServices>) {
1708 let cloned = self.clone();
1709 tokio::spawn(async move {
1710 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1711 loop {
1712 if shutdown_receiver.has_changed().unwrap_or(true) {
1713 return;
1714 }
1715 let mut log_stream = match cloned.node_api.stream_log_messages().await {
1716 Ok(log_stream) => log_stream,
1717 Err(e) => {
1718 warn!("stream log messages returned error: {e:?}");
1719 tokio::select! {
1720 _ = sleep(Duration::from_secs(1)) => {
1721 continue
1722 }
1723 _ = shutdown_receiver.changed() => {
1724 debug!("Invoice tracking task has completed");
1725 return;
1726 }
1727 };
1728 }
1729 };
1730
1731 loop {
1732 let log_message_res = tokio::select! {
1733 log_message_res = log_stream.next() => log_message_res,
1734 _ = shutdown_receiver.changed() => {
1735 debug!("Track logs task has completed");
1736 return;
1737 }
1738 };
1739
1740 match log_message_res {
1741 Some(l) => info!("node-logs: {l}"),
1742 None => {
1743 break;
1745 }
1746 };
1747 }
1748
1749 tokio::select! {
1750 _ = sleep(Duration::from_secs(1)) => {
1751 continue
1752 }
1753 _ = shutdown_receiver.changed() => {
1754 debug!("Invoice tracking task has completed");
1755 return;
1756 }
1757 };
1758 }
1759 });
1760 }
1761
1762 async fn track_new_blocks(self: &Arc<BreezServices>) {
1763 let cloned = self.clone();
1764 tokio::spawn(async move {
1765 let mut current_block: u32 = 0;
1766 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1767 let mut interval = tokio::time::interval(Duration::from_secs(30));
1768 interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
1769 loop {
1770 tokio::select! {
1771 _ = interval.tick() => { }
1772
1773 _ = shutdown_receiver.changed() => {
1774 debug!("New blocks task has completed");
1775 return;
1776 }
1777 }
1778
1779 let next_block = match cloned.chain_service.current_tip().await {
1780 Ok(next_block) => next_block,
1781 Err(e) => {
1782 error!("failed to fetch next block {e}");
1783 continue;
1784 }
1785 };
1786
1787 debug!("got tip {next_block:?}");
1788 if next_block > current_block {
1789 _ = cloned.sync().await;
1790 _ = cloned
1791 .on_event(BreezEvent::NewBlock { block: next_block })
1792 .await;
1793 }
1794 current_block = next_block
1795 }
1796 });
1797 }
1798
1799 async fn init_chainservice_urls(&self) -> Result<()> {
1800 let breez_server = Arc::new(BreezServer::new(
1801 PRODUCTION_BREEZSERVER_URL.to_string(),
1802 None,
1803 )?);
1804 let persister = &self.persister;
1805
1806 let cloned_breez_server = breez_server.clone();
1807 let cloned_persister = persister.clone();
1808 tokio::spawn(async move {
1809 match cloned_breez_server.fetch_mempoolspace_urls().await {
1810 Ok(fresh_urls) => {
1811 if let Err(e) = cloned_persister.set_mempoolspace_base_urls(fresh_urls) {
1812 error!("Failed to cache mempool.space URLs: {e}");
1813 }
1814 }
1815 Err(e) => error!("Failed to fetch mempool.space URLs: {e}"),
1816 }
1817 });
1818
1819 Ok(())
1820 }
1821
1822 pub fn init_logging(log_dir: &str, app_logger: Option<Box<dyn log::Log>>) -> Result<()> {
1851 let target_log_file = Box::new(
1852 OpenOptions::new()
1853 .create(true)
1854 .append(true)
1855 .open(format!("{log_dir}/sdk.log"))
1856 .map_err(|e| anyhow!("Can't create log file: {e}"))?,
1857 );
1858 let logger = env_logger::Builder::new()
1859 .target(env_logger::Target::Pipe(target_log_file))
1860 .parse_filters(
1861 r#"
1862 info,
1863 breez_sdk_core=debug,
1864 sdk_common=debug,
1865 gl_client=debug,
1866 h2=warn,
1867 hyper=warn,
1868 lightning_signer=warn,
1869 reqwest=warn,
1870 rustls=warn,
1871 rustyline=warn,
1872 vls_protocol_signer=warn
1873 "#,
1874 )
1875 .format(|buf, record| {
1876 writeln!(
1877 buf,
1878 "[{} {} {}:{}] {}",
1879 Local::now().format("%Y-%m-%d %H:%M:%S%.3f"),
1880 record.level(),
1881 record.module_path().unwrap_or("unknown"),
1882 record.line().unwrap_or(0),
1883 record.args()
1884 )
1885 })
1886 .build();
1887
1888 let global_logger = GlobalSdkLogger {
1889 logger,
1890 log_listener: app_logger,
1891 };
1892
1893 log::set_boxed_logger(Box::new(global_logger))
1894 .map_err(|e| anyhow!("Failed to set global logger: {e}"))?;
1895 log::set_max_level(LevelFilter::Trace);
1896
1897 Ok(())
1898 }
1899
1900 async fn lookup_chain_service_closing_outspend(
1901 &self,
1902 channel: crate::models::Channel,
1903 ) -> Result<Option<Outspend>> {
1904 match channel.funding_outnum {
1905 None => Ok(None),
1906 Some(outnum) => {
1907 let outspends = self
1909 .chain_service
1910 .transaction_outspends(channel.funding_txid.clone())
1911 .await?;
1912
1913 Ok(outspends.get(outnum as usize).cloned())
1914 }
1915 }
1916 }
1917
1918 async fn lookup_channel_closing_data(
1922 &self,
1923 channel: &crate::models::Channel,
1924 ) -> Result<(Option<u64>, Option<String>)> {
1925 let maybe_outspend_res = self
1926 .lookup_chain_service_closing_outspend(channel.clone())
1927 .await;
1928 let maybe_outspend: Option<Outspend> = match maybe_outspend_res {
1929 Ok(s) => s,
1930 Err(e) => {
1931 error!("Failed to lookup channel closing data: {e:?}");
1932 None
1933 }
1934 };
1935
1936 let maybe_closed_at = maybe_outspend
1937 .clone()
1938 .and_then(|outspend| outspend.status)
1939 .and_then(|s| s.block_time);
1940 let maybe_closing_txid = maybe_outspend.and_then(|outspend| outspend.txid);
1941
1942 Ok((maybe_closed_at, maybe_closing_txid))
1943 }
1944
1945 async fn closed_channel_to_transaction(
1946 &self,
1947 channel: crate::models::Channel,
1948 ) -> Result<Payment> {
1949 let (payment_time, closing_txid) = match (channel.closed_at, channel.closing_txid.clone()) {
1950 (Some(closed_at), Some(closing_txid)) => (closed_at as i64, Some(closing_txid)),
1951 (_, _) => {
1952 let (maybe_closed_at, maybe_closing_txid) =
1954 self.lookup_channel_closing_data(&channel).await?;
1955
1956 let processed_closed_at = match maybe_closed_at {
1957 None => {
1958 warn!("Blocktime could not be determined for from closing outspend, defaulting closed_at to epoch time");
1959 SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()
1960 }
1961 Some(block_time) => block_time,
1962 };
1963
1964 let mut updated_channel = channel.clone();
1965 updated_channel.closed_at = Some(processed_closed_at);
1966 updated_channel.closing_txid.clone_from(&maybe_closing_txid);
1968 self.persister.insert_or_update_channel(updated_channel)?;
1969
1970 (processed_closed_at as i64, maybe_closing_txid)
1971 }
1972 };
1973
1974 Ok(Payment {
1975 id: channel.funding_txid.clone(),
1976 payment_type: PaymentType::ClosedChannel,
1977 payment_time,
1978 amount_msat: channel.local_balance_msat,
1979 fee_msat: 0,
1980 status: match channel.state {
1981 ChannelState::PendingClose => PaymentStatus::Pending,
1982 _ => PaymentStatus::Complete,
1983 },
1984 description: Some("Closed Channel".to_string()),
1985 details: PaymentDetails::ClosedChannel {
1986 data: ClosedChannelPaymentDetails {
1987 short_channel_id: channel.short_channel_id,
1988 state: channel.state,
1989 funding_txid: channel.funding_txid,
1990 closing_txid,
1991 },
1992 },
1993 error: None,
1994 metadata: None,
1995 })
1996 }
1997
1998 pub async fn register_webhook(&self, webhook_url: String) -> SdkResult<()> {
2009 info!("Registering for webhook notifications");
2010 let is_new_webhook_url = match self.persister.get_webhook_url()? {
2011 None => true,
2012 Some(cached_webhook_url) => cached_webhook_url != webhook_url,
2013 };
2014 match is_new_webhook_url {
2015 false => debug!("Webhook URL not changed, no need to (re-)register for monitored swap tx notifications"),
2016 true => {
2017 for swap in self.btc_receive_swapper.list_swaps(ListSwapsRequest {
2018 status: Some(SwapStatus::unexpired()),
2019 ..Default::default()
2020 })?
2021 {
2022 let swap_address = &swap.bitcoin_address;
2023 info!("Found non-refundable monitored swap with address {swap_address}, registering for onchain tx notifications");
2024 self.register_onchain_tx_notification(swap_address, &webhook_url)
2025 .await?;
2026 }
2027
2028 for rev_swap in self
2029 .btc_send_swapper
2030 .list_monitored()
2031 .await?
2032 .iter()
2033 {
2034 let lockup_address = &rev_swap.get_lockup_address(self.config.network)?.to_string();
2035 info!("Found monitored reverse swap with address {lockup_address}, registering for onchain tx notifications");
2036 self.register_onchain_tx_notification(lockup_address, &webhook_url)
2037 .await?;
2038 }
2039 }
2040 }
2041
2042 self.register_payment_notifications(webhook_url.clone())
2045 .await?;
2046
2047 self.persister.set_webhook_url(webhook_url)?;
2051 Ok(())
2052 }
2053
2054 pub async fn unregister_webhook(&self, webhook_url: String) -> SdkResult<()> {
2064 info!("Unregistering for webhook notifications");
2065 self.unregister_onchain_tx_notifications(&webhook_url)
2066 .await?;
2067 self.unregister_payment_notifications(webhook_url).await?;
2068 self.persister.remove_webhook_url()?;
2069 Ok(())
2070 }
2071
2072 async fn register_payment_notifications(&self, webhook_url: String) -> SdkResult<()> {
2078 let message = webhook_url.clone();
2079 let sign_request = SignMessageRequest { message };
2080 let sign_response = self.sign_message(sign_request).await?;
2081
2082 let mut error_found = false;
2084 for lsp_info in get_notification_lsps(
2085 self.persister.clone(),
2086 self.lsp_api.clone(),
2087 self.node_api.clone(),
2088 )
2089 .await?
2090 {
2091 let lsp_id = lsp_info.id;
2092 let res = self
2093 .lsp_api
2094 .register_payment_notifications(
2095 lsp_id.clone(),
2096 lsp_info.lsp_pubkey,
2097 webhook_url.clone(),
2098 sign_response.signature.clone(),
2099 )
2100 .await;
2101 if res.is_err() {
2102 error_found = true;
2103 warn!("Failed to register notifications for LSP {lsp_id}: {res:?}");
2104 }
2105 }
2106
2107 match error_found {
2108 true => Err(SdkError::generic(
2109 "Failed to register notifications for at least one LSP, see logs for details",
2110 )),
2111 false => Ok(()),
2112 }
2113 }
2114
2115 async fn unregister_payment_notifications(&self, webhook_url: String) -> SdkResult<()> {
2120 let message = webhook_url.clone();
2121 let sign_request = SignMessageRequest { message };
2122 let sign_response = self.sign_message(sign_request).await?;
2123
2124 let mut error_found = false;
2126 for lsp_info in get_notification_lsps(
2127 self.persister.clone(),
2128 self.lsp_api.clone(),
2129 self.node_api.clone(),
2130 )
2131 .await?
2132 {
2133 let lsp_id = lsp_info.id;
2134 let res = self
2135 .lsp_api
2136 .unregister_payment_notifications(
2137 lsp_id.clone(),
2138 lsp_info.lsp_pubkey,
2139 webhook_url.clone(),
2140 sign_response.signature.clone(),
2141 )
2142 .await;
2143 if res.is_err() {
2144 error_found = true;
2145 warn!("Failed to un-register notifications for LSP {lsp_id}: {res:?}");
2146 }
2147 }
2148
2149 match error_found {
2150 true => Err(SdkError::generic(
2151 "Failed to un-register notifications for at least one LSP, see logs for details",
2152 )),
2153 false => Ok(()),
2154 }
2155 }
2156
2157 async fn register_onchain_tx_notification(
2160 &self,
2161 address: &str,
2162 webhook_url: &str,
2163 ) -> SdkResult<()> {
2164 let url = format!("{}/api/v1/register", self.config.chainnotifier_url);
2165 let headers = HashMap::from([("Content-Type".to_string(), "application/json".to_string())]);
2166 let body = json!({
2167 "address": address,
2168 "webhook": webhook_url
2169 })
2170 .to_string();
2171 self.rest_client
2172 .post(&url, Some(headers), Some(body))
2173 .await
2174 .map(|_| ())
2175 .map_err(|e| SdkError::ServiceConnectivity {
2176 err: format!("Failed to register for tx confirmation notifications: {e}"),
2177 })
2178 }
2179
2180 async fn unregister_onchain_tx_notifications(&self, webhook_url: &str) -> SdkResult<()> {
2182 let url = format!("{}/api/v1/unregister", self.config.chainnotifier_url);
2183 let headers = HashMap::from([("Content-Type".to_string(), "application/json".to_string())]);
2184 let body = json!({
2185 "webhook": webhook_url
2186 })
2187 .to_string();
2188 self.rest_client
2189 .post(&url, Some(headers), Some(body))
2190 .await
2191 .map(|_| ())
2192 .map_err(|e| SdkError::ServiceConnectivity {
2193 err: format!("Failed to unregister for tx confirmation notifications: {e}"),
2194 })
2195 }
2196
2197 async fn generate_sdk_diagnostic_data(&self) -> SdkResult<Value> {
2198 let (sdk_version, sdk_git_hash) = Self::get_sdk_version();
2199 let version = format!("SDK v{sdk_version} ({sdk_git_hash})");
2200 let state = crate::serializer::value::to_value(&self.persister.get_node_state()?)?;
2201 let payments = crate::serializer::value::to_value(
2202 &self
2203 .persister
2204 .list_payments(ListPaymentsRequest::default())?,
2205 )?;
2206 let channels = crate::serializer::value::to_value(&self.persister.list_channels()?)?;
2207 let settings = crate::serializer::value::to_value(&self.persister.list_settings()?)?;
2208 let reverse_swaps = crate::serializer::value::to_value(
2209 self.persister.list_reverse_swaps().map(sanitize_vec)?,
2210 )?;
2211 let swaps = crate::serializer::value::to_value(
2212 self.btc_receive_swapper
2213 .list_swaps(ListSwapsRequest::default())
2214 .map(sanitize_vec)?,
2215 )?;
2216 let lsp_id = crate::serializer::value::to_value(&self.persister.get_lsp_id()?)?;
2217
2218 let res = json!({
2219 "version": version,
2220 "node_state": state,
2221 "payments": payments,
2222 "channels": channels,
2223 "settings": settings,
2224 "reverse_swaps": reverse_swaps,
2225 "swaps": swaps,
2226 "lsp_id": lsp_id,
2227 });
2228 Ok(res)
2229 }
2230}
2231
2232struct GlobalSdkLogger {
2233 logger: env_logger::Logger,
2235 log_listener: Option<Box<dyn log::Log>>,
2237}
2238impl log::Log for GlobalSdkLogger {
2239 fn enabled(&self, metadata: &Metadata) -> bool {
2240 metadata.level() <= log::Level::Trace
2241 }
2242
2243 fn log(&self, record: &Record) {
2244 if self.enabled(record.metadata()) {
2245 self.logger.log(record);
2246
2247 if let Some(s) = &self.log_listener.as_ref() {
2248 if s.enabled(record.metadata()) {
2249 s.log(record);
2250 }
2251 }
2252 }
2253 }
2254
2255 fn flush(&self) {}
2256}
2257
2258struct BreezServicesBuilder {
2260 config: Config,
2261 node_api: Option<Arc<dyn NodeAPI>>,
2262 backup_transport: Option<Arc<dyn BackupTransport>>,
2263 seed: Option<Vec<u8>>,
2264 lsp_api: Option<Arc<dyn LspAPI>>,
2265 fiat_api: Option<Arc<dyn FiatAPI>>,
2266 persister: Option<Arc<SqliteStorage>>,
2267 rest_client: Option<Arc<dyn RestClient>>,
2268 support_api: Option<Arc<dyn SupportAPI>>,
2269 swapper_api: Option<Arc<dyn SwapperAPI>>,
2270 taproot_swapper_api: Option<Arc<dyn TaprootSwapperAPI>>,
2271 reverse_swapper_api: Option<Arc<dyn ReverseSwapperRoutingAPI>>,
2273 reverse_swap_service_api: Option<Arc<dyn ReverseSwapServiceAPI>>,
2275 buy_bitcoin_api: Option<Arc<dyn BuyBitcoinApi>>,
2276}
2277
2278#[allow(dead_code)]
2279impl BreezServicesBuilder {
2280 pub fn new(config: Config) -> BreezServicesBuilder {
2281 BreezServicesBuilder {
2282 config,
2283 node_api: None,
2284 seed: None,
2285 lsp_api: None,
2286 fiat_api: None,
2287 persister: None,
2288 rest_client: None,
2289 support_api: None,
2290 swapper_api: None,
2291 taproot_swapper_api: None,
2292 reverse_swapper_api: None,
2293 reverse_swap_service_api: None,
2294 buy_bitcoin_api: None,
2295 backup_transport: None,
2296 }
2297 }
2298
2299 pub fn node_api(&mut self, node_api: Arc<dyn NodeAPI>) -> &mut Self {
2300 self.node_api = Some(node_api);
2301 self
2302 }
2303
2304 pub fn lsp_api(&mut self, lsp_api: Arc<dyn LspAPI>) -> &mut Self {
2305 self.lsp_api = Some(lsp_api.clone());
2306 self
2307 }
2308
2309 pub fn fiat_api(&mut self, fiat_api: Arc<dyn FiatAPI>) -> &mut Self {
2310 self.fiat_api = Some(fiat_api.clone());
2311 self
2312 }
2313
2314 pub fn buy_bitcoin_api(&mut self, buy_bitcoin_api: Arc<dyn BuyBitcoinApi>) -> &mut Self {
2315 self.buy_bitcoin_api = Some(buy_bitcoin_api.clone());
2316 self
2317 }
2318
2319 pub fn persister(&mut self, persister: Arc<SqliteStorage>) -> &mut Self {
2320 self.persister = Some(persister);
2321 self
2322 }
2323
2324 pub fn support_api(&mut self, support_api: Arc<dyn SupportAPI>) -> &mut Self {
2325 self.support_api = Some(support_api.clone());
2326 self
2327 }
2328
2329 pub fn rest_client(&mut self, rest_client: Arc<dyn RestClient>) -> &mut Self {
2330 self.rest_client = Some(rest_client.clone());
2331 self
2332 }
2333
2334 pub fn swapper_api(&mut self, swapper_api: Arc<dyn SwapperAPI>) -> &mut Self {
2335 self.swapper_api = Some(swapper_api.clone());
2336 self
2337 }
2338
2339 pub fn taproot_swapper_api(&mut self, swapper_api: Arc<dyn TaprootSwapperAPI>) -> &mut Self {
2340 self.taproot_swapper_api = Some(swapper_api.clone());
2341 self
2342 }
2343
2344 pub fn reverse_swapper_api(
2345 &mut self,
2346 reverse_swapper_api: Arc<dyn ReverseSwapperRoutingAPI>,
2347 ) -> &mut Self {
2348 self.reverse_swapper_api = Some(reverse_swapper_api.clone());
2349 self
2350 }
2351
2352 pub fn reverse_swap_service_api(
2353 &mut self,
2354 reverse_swap_service_api: Arc<dyn ReverseSwapServiceAPI>,
2355 ) -> &mut Self {
2356 self.reverse_swap_service_api = Some(reverse_swap_service_api.clone());
2357 self
2358 }
2359
2360 pub fn backup_transport(&mut self, backup_transport: Arc<dyn BackupTransport>) -> &mut Self {
2361 self.backup_transport = Some(backup_transport.clone());
2362 self
2363 }
2364
2365 pub fn seed(&mut self, seed: Vec<u8>) -> &mut Self {
2366 self.seed = Some(seed);
2367 self
2368 }
2369
2370 pub async fn build(
2371 &self,
2372 restore_only: Option<bool>,
2373 event_listener: Option<Box<dyn EventListener>>,
2374 ) -> BreezServicesResult<Arc<BreezServices>> {
2375 if self.node_api.is_none() && self.seed.is_none() {
2376 return Err(ConnectError::Generic {
2377 err: "Either node_api or both credentials and seed should be provided".into(),
2378 });
2379 }
2380
2381 let persister = self
2383 .persister
2384 .clone()
2385 .unwrap_or_else(|| Arc::new(SqliteStorage::new(self.config.working_dir.clone())));
2386 persister.init()?;
2387
2388 let breez_server = Arc::new(
2390 BreezServer::new(self.config.breezserver.clone(), self.config.api_key.clone())
2391 .map_err(|e| ConnectError::ServiceConnectivity {
2392 err: format!("Failed to create BreezServer: {e}"),
2393 })?,
2394 );
2395
2396 let mut node_api = self.node_api.clone();
2397 let mut backup_transport = self.backup_transport.clone();
2398 let mut lsp_api = self.lsp_api.clone();
2399 let mut receiver = None;
2400 if node_api.is_none() {
2401 let node_impls = node_builder::build_node(
2402 self.config.clone(),
2403 self.seed.clone().unwrap(),
2404 restore_only,
2405 persister.clone(),
2406 )
2407 .await?;
2408 node_api = Some(node_impls.node);
2409 backup_transport = backup_transport.or(Some(node_impls.backup_transport));
2410 lsp_api = lsp_api.or(node_impls.lsp);
2411 receiver = node_impls.receiver;
2412 }
2413 let lsp_api = lsp_api.unwrap_or_else(|| breez_server.clone());
2414
2415 if backup_transport.is_none() {
2416 return Err(ConnectError::Generic {
2417 err: "State synchronizer should be provided".into(),
2418 });
2419 }
2420
2421 let unwrapped_node_api = node_api.unwrap();
2422 let unwrapped_backup_transport = backup_transport.unwrap();
2423
2424 let backup_encryption_key = unwrapped_node_api
2426 .derive_bip32_key(vec![
2427 ChildNumber::from_hardened_idx(139)?,
2428 ChildNumber::from(0),
2429 ])
2430 .await?;
2431
2432 let legacy_backup_encryption_key = unwrapped_node_api
2435 .legacy_derive_bip32_key(vec![
2436 ChildNumber::from_hardened_idx(139)?,
2437 ChildNumber::from(0),
2438 ])
2439 .await?;
2440 let backup_watcher = BackupWatcher::new(
2441 self.config.clone(),
2442 unwrapped_backup_transport.clone(),
2443 persister.clone(),
2444 backup_encryption_key.to_priv().to_bytes(),
2445 legacy_backup_encryption_key.to_priv().to_bytes(),
2446 );
2447
2448 let cloned_breez_server = breez_server.clone();
2450 tokio::spawn(async move {
2451 if let Err(e) = cloned_breez_server.ping().await {
2452 error!("Failed to ping breez server: {e}");
2453 }
2454 });
2455
2456 let current_lsp_id = persister.get_lsp_id()?;
2457 if current_lsp_id.is_none() && self.config.default_lsp_id.is_some() {
2458 persister.set_lsp(self.config.default_lsp_id.clone().unwrap(), None)?;
2459 }
2460
2461 let payment_receiver = receiver.unwrap_or_else(|| {
2462 Arc::new(PaymentReceiver {
2463 config: self.config.clone(),
2464 node_api: unwrapped_node_api.clone(),
2465 lsp: lsp_api.clone(),
2466 persister: persister.clone(),
2467 })
2468 });
2469
2470 let rest_client: Arc<dyn RestClient> = match self.rest_client.clone() {
2471 Some(rest_client) => rest_client,
2472 None => Arc::new(ReqwestRestClient::new()?),
2473 };
2474
2475 let mempoolspace_urls = match self.config.mempoolspace_url.clone() {
2477 None => {
2478 let cached = persister.get_mempoolspace_base_urls()?;
2479 match cached.len() {
2480 0 => {
2482 let fresh_urls = breez_server
2483 .fetch_mempoolspace_urls()
2484 .await
2485 .unwrap_or(vec![DEFAULT_MEMPOOL_SPACE_URL.into()]);
2486 persister.set_mempoolspace_base_urls(fresh_urls.clone())?;
2487 fresh_urls
2488 }
2489 _ => cached,
2491 }
2492 }
2493 Some(mempoolspace_url_from_config) => vec![mempoolspace_url_from_config],
2494 };
2495 let chain_service = Arc::new(RedundantChainService::from_base_urls(
2496 rest_client.clone(),
2497 mempoolspace_urls,
2498 ));
2499
2500 let btc_receive_swapper = Arc::new(BTCReceiveSwap::new(BTCReceiveSwapParameters {
2501 chain_service: chain_service.clone(),
2502 payment_storage: persister.clone(),
2503 network: self.config.network.into(),
2504 node_api: unwrapped_node_api.clone(),
2505 node_state_storage: persister.clone(),
2506 payment_receiver: payment_receiver.clone(),
2507 segwit_swapper_api: self
2508 .swapper_api
2509 .clone()
2510 .unwrap_or_else(|| breez_server.clone()),
2511 swap_storage: persister.clone(),
2512 taproot_swapper_api: self
2513 .taproot_swapper_api
2514 .clone()
2515 .unwrap_or_else(|| breez_server.clone()),
2516 }));
2517
2518 let btc_send_swapper = Arc::new(BTCSendSwap::new(
2519 self.config.clone(),
2520 self.reverse_swapper_api
2521 .clone()
2522 .unwrap_or_else(|| breez_server.clone()),
2523 self.reverse_swap_service_api
2524 .clone()
2525 .unwrap_or_else(|| Arc::new(BoltzApi::new(rest_client.clone()))),
2526 persister.clone(),
2527 chain_service.clone(),
2528 unwrapped_node_api.clone(),
2529 ));
2530
2531 let (shutdown_sender, _shutdown_receiver) = watch::channel::<()>(());
2533
2534 let buy_bitcoin_api = self
2535 .buy_bitcoin_api
2536 .clone()
2537 .unwrap_or_else(|| Arc::new(BuyBitcoinService::new(breez_server.clone())));
2538
2539 let breez_services = Arc::new(BreezServices {
2541 config: self.config.clone(),
2542 started: Mutex::new(false),
2543 node_api: unwrapped_node_api.clone(),
2544 lsp_api,
2545 fiat_api: self
2546 .fiat_api
2547 .clone()
2548 .unwrap_or_else(|| breez_server.clone()),
2549 support_api: self
2550 .support_api
2551 .clone()
2552 .unwrap_or_else(|| breez_server.clone()),
2553 buy_bitcoin_api,
2554 chain_service,
2555 persister: persister.clone(),
2556 rest_client,
2557 btc_receive_swapper,
2558 btc_send_swapper,
2559 payment_receiver,
2560 event_listener,
2561 backup_watcher: Arc::new(backup_watcher),
2562 shutdown_sender,
2563 });
2564
2565 Ok(breez_services)
2566 }
2567}
2568
2569pub fn mnemonic_to_seed(phrase: String) -> Result<Vec<u8>> {
2573 let mnemonic = Mnemonic::from_phrase(&phrase, Language::English)?;
2574 let seed = Seed::new(&mnemonic, "");
2575 Ok(seed.as_bytes().to_vec())
2576}
2577
2578pub struct OpenChannelParams {
2579 pub payer_amount_msat: u64,
2580 pub opening_fee_params: models::OpeningFeeParams,
2581}
2582
2583#[tonic::async_trait]
2584pub trait Receiver: Send + Sync {
2585 fn open_channel_needed(&self, amount_msat: u64) -> Result<bool, ReceivePaymentError>;
2586 async fn receive_payment(
2587 &self,
2588 req: ReceivePaymentRequest,
2589 ) -> Result<ReceivePaymentResponse, ReceivePaymentError>;
2590 async fn wrap_node_invoice(
2591 &self,
2592 invoice: &str,
2593 params: Option<OpenChannelParams>,
2594 lsp_info: Option<LspInformation>,
2595 ) -> Result<String, ReceivePaymentError>;
2596}
2597
2598pub(crate) struct PaymentReceiver {
2599 config: Config,
2600 node_api: Arc<dyn NodeAPI>,
2601 lsp: Arc<dyn LspAPI>,
2602 persister: Arc<SqliteStorage>,
2603}
2604
2605#[tonic::async_trait]
2606impl Receiver for PaymentReceiver {
2607 fn open_channel_needed(&self, amount_msat: u64) -> Result<bool, ReceivePaymentError> {
2608 let node_state = self
2609 .persister
2610 .get_node_state()?
2611 .ok_or(ReceivePaymentError::Generic {
2612 err: "Node info not found".into(),
2613 })?;
2614 Ok(node_state.max_receivable_single_payment_amount_msat < amount_msat)
2615 }
2616
2617 async fn receive_payment(
2618 &self,
2619 req: ReceivePaymentRequest,
2620 ) -> Result<ReceivePaymentResponse, ReceivePaymentError> {
2621 let lsp_info = get_lsp(self.persister.clone(), self.lsp.clone()).await?;
2622 let expiry = req.expiry.unwrap_or(INVOICE_PAYMENT_FEE_EXPIRY_SECONDS);
2623
2624 ensure_sdk!(
2625 req.amount_msat > 0,
2626 ReceivePaymentError::InvalidAmount {
2627 err: "Receive amount must be more than 0".into()
2628 }
2629 );
2630
2631 let mut destination_invoice_amount_msat = req.amount_msat;
2632 let mut channel_opening_fee_params = None;
2633 let mut channel_fees_msat = None;
2634
2635 let open_channel_needed = self.open_channel_needed(req.amount_msat)?;
2637 if open_channel_needed {
2638 info!("We need to open a channel");
2639
2640 let ofp = match req.opening_fee_params {
2642 Some(fee_params) => fee_params,
2643 None => lsp_info.cheapest_open_channel_fee(expiry)?.clone(),
2644 };
2645
2646 channel_opening_fee_params = Some(ofp.clone());
2647 channel_fees_msat = Some(ofp.get_channel_fees_msat_for(req.amount_msat));
2648 if let Some(channel_fees_msat) = channel_fees_msat {
2649 info!("zero-conf fee calculation option: lsp fee rate (proportional): {}: (minimum {}), total fees for channel: {}",
2650 ofp.proportional, ofp.min_msat, channel_fees_msat);
2651
2652 if req.amount_msat < channel_fees_msat + 1000 {
2653 return Err(
2654 ReceivePaymentError::InvalidAmount{err: format!(
2655 "Amount should be more than the minimum fees {channel_fees_msat} msat, but is {} msat",
2656 req.amount_msat
2657 )}
2658 );
2659 }
2660 destination_invoice_amount_msat = req.amount_msat - channel_fees_msat;
2662 }
2663 }
2664
2665 info!("Creating invoice on NodeAPI");
2666 let invoice = self
2667 .node_api
2668 .create_invoice(CreateInvoiceRequest {
2669 amount_msat: destination_invoice_amount_msat,
2670 description: req.description,
2671 payer_amount_msat: match open_channel_needed {
2672 true => Some(req.amount_msat),
2673 false => None,
2674 },
2675 preimage: req.preimage,
2676 use_description_hash: req.use_description_hash,
2677 expiry: Some(expiry),
2678 cltv: Some(req.cltv.unwrap_or(144)),
2679 })
2680 .await?;
2681 info!("Invoice created {invoice}");
2682
2683 let open_channel_params = match open_channel_needed {
2684 true => Some(OpenChannelParams {
2685 payer_amount_msat: req.amount_msat,
2686 opening_fee_params: channel_opening_fee_params.clone().ok_or(
2687 ReceivePaymentError::Generic {
2688 err: "We need to open a channel, but no channel opening fee params found"
2689 .into(),
2690 },
2691 )?,
2692 }),
2693 false => None,
2694 };
2695
2696 let invoice = self
2697 .wrap_node_invoice(&invoice, open_channel_params, Some(lsp_info))
2698 .await?;
2699 let parsed_invoice = parse_invoice(&invoice)?;
2700
2701 Ok(ReceivePaymentResponse {
2703 ln_invoice: parsed_invoice,
2704 opening_fee_params: channel_opening_fee_params,
2705 opening_fee_msat: channel_fees_msat,
2706 })
2707 }
2708
2709 async fn wrap_node_invoice(
2710 &self,
2711 invoice: &str,
2712 params: Option<OpenChannelParams>,
2713 lsp_info: Option<LspInformation>,
2714 ) -> Result<String, ReceivePaymentError> {
2715 let lsp_info = match lsp_info {
2716 Some(lsp_info) => lsp_info,
2717 None => get_lsp(self.persister.clone(), self.lsp.clone()).await?,
2718 };
2719
2720 match params {
2721 Some(params) => {
2722 self.wrap_open_channel_invoice(invoice, params, &lsp_info)
2723 .await
2724 }
2725 None => self.ensure_hint(invoice, &lsp_info).await,
2726 }
2727 }
2728}
2729
2730impl PaymentReceiver {
2731 async fn ensure_hint(
2732 &self,
2733 invoice: &str,
2734 lsp_info: &LspInformation,
2735 ) -> Result<String, ReceivePaymentError> {
2736 info!("Getting routing hints from node");
2737 let (mut hints, has_public_channel) = self.node_api.get_routing_hints(lsp_info).await?;
2738 if !has_public_channel && hints.is_empty() {
2739 return Err(ReceivePaymentError::InvoiceNoRoutingHints {
2740 err: "Must have at least one active channel".into(),
2741 });
2742 }
2743
2744 let parsed_invoice = parse_invoice(invoice)?;
2745
2746 info!("Existing routing hints {:?}", parsed_invoice.routing_hints);
2748
2749 if let Some(lsp_hint) = Self::limit_and_extract_lsp_hint(&mut hints, lsp_info) {
2751 if parsed_invoice.contains_hint_for_node(lsp_info.pubkey.as_str()) {
2752 return Ok(String::from(invoice));
2753 }
2754
2755 info!("Adding lsp hint: {lsp_hint:?}");
2756 let modified =
2757 add_routing_hints(invoice, true, &vec![lsp_hint], parsed_invoice.amount_msat)?;
2758
2759 let invoice = self.node_api.sign_invoice(modified).await?;
2760 info!("Signed invoice with hint = {invoice}");
2761 return Ok(invoice);
2762 }
2763
2764 if parsed_invoice.routing_hints.is_empty() {
2765 info!("Adding custom hints: {hints:?}");
2766 let modified = add_routing_hints(invoice, false, &hints, parsed_invoice.amount_msat)?;
2767 let invoice = self.node_api.sign_invoice(modified).await?;
2768 info!("Signed invoice with hints = {invoice}");
2769 return Ok(invoice);
2770 }
2771
2772 Ok(String::from(invoice))
2773 }
2774
2775 async fn wrap_open_channel_invoice(
2776 &self,
2777 invoice: &str,
2778 params: OpenChannelParams,
2779 lsp_info: &LspInformation,
2780 ) -> Result<String, ReceivePaymentError> {
2781 let parsed_invoice = parse_invoice(invoice)?;
2782 let open_channel_hint = RouteHint {
2783 hops: vec![RouteHintHop {
2784 src_node_id: lsp_info.pubkey.clone(),
2785 short_channel_id: "1x0x0".to_string(),
2786 fees_base_msat: lsp_info.base_fee_msat as u32,
2787 fees_proportional_millionths: (lsp_info.fee_rate * 1000000.0) as u32,
2788 cltv_expiry_delta: lsp_info.time_lock_delta as u64,
2789 htlc_minimum_msat: Some(lsp_info.min_htlc_msat as u64),
2790 htlc_maximum_msat: None,
2791 }],
2792 };
2793 info!("Adding open channel hint: {open_channel_hint:?}");
2794 let invoice_with_hint = add_routing_hints(
2795 invoice,
2796 false,
2797 &vec![open_channel_hint],
2798 Some(params.payer_amount_msat),
2799 )?;
2800 let signed_invoice = self.node_api.sign_invoice(invoice_with_hint).await?;
2801
2802 info!("Registering payment with LSP");
2803 let api_key = self.config.api_key.clone().unwrap_or_default();
2804 let api_key_hash = format!("{:x}", sha256::Hash::hash(api_key.as_bytes()));
2805
2806 self.lsp
2807 .register_payment(
2808 lsp_info.id.clone(),
2809 lsp_info.lsp_pubkey.clone(),
2810 grpc::PaymentInformation {
2811 payment_hash: hex::decode(parsed_invoice.payment_hash.clone())
2812 .map_err(|e| anyhow!("Failed to decode hex payment hash: {e}"))?,
2813 payment_secret: parsed_invoice.payment_secret.clone(),
2814 destination: hex::decode(parsed_invoice.payee_pubkey.clone())
2815 .map_err(|e| anyhow!("Failed to decode hex payee pubkey: {e}"))?,
2816 incoming_amount_msat: params.payer_amount_msat as i64,
2817 outgoing_amount_msat: parsed_invoice
2818 .amount_msat
2819 .ok_or(anyhow!("Open channel invoice must have an amount"))?
2820 as i64,
2821 tag: json!({ "apiKeyHash": api_key_hash }).to_string(),
2822 opening_fee_params: Some(params.opening_fee_params.into()),
2823 },
2824 )
2825 .await?;
2826 self.persister.insert_open_channel_payment_info(
2828 &parsed_invoice.payment_hash,
2829 params.payer_amount_msat,
2830 &signed_invoice,
2831 )?;
2832
2833 Ok(signed_invoice)
2834 }
2835
2836 fn limit_and_extract_lsp_hint(
2837 routing_hints: &mut Vec<RouteHint>,
2838 lsp_info: &LspInformation,
2839 ) -> Option<RouteHint> {
2840 let mut lsp_hint: Option<RouteHint> = None;
2841 if let Some(lsp_index) = routing_hints.iter().position(|r| {
2842 r.hops
2843 .iter()
2844 .any(|h| h.src_node_id == lsp_info.pubkey.clone())
2845 }) {
2846 lsp_hint = Some(routing_hints.remove(lsp_index));
2847 }
2848 if routing_hints.len() > 3 {
2849 routing_hints.drain(3..);
2850 }
2851 lsp_hint
2852 }
2853}
2854
2855async fn get_lsp(
2857 persister: Arc<SqliteStorage>,
2858 lsp_api: Arc<dyn LspAPI>,
2859) -> SdkResult<LspInformation> {
2860 let lsp_id = persister
2861 .get_lsp_id()?
2862 .ok_or(SdkError::generic("No LSP ID found"))?;
2863
2864 get_lsp_by_id(persister, lsp_api, lsp_id.as_str())
2865 .await?
2866 .ok_or_else(|| SdkError::Generic {
2867 err: format!("No LSP found for id {lsp_id}"),
2868 })
2869}
2870
2871async fn get_lsps(
2872 persister: Arc<SqliteStorage>,
2873 lsp_api: Arc<dyn LspAPI>,
2874) -> SdkResult<Vec<LspInformation>> {
2875 let node_pubkey = persister
2876 .get_node_state()?
2877 .ok_or(SdkError::generic("Node info not found"))?
2878 .id;
2879
2880 lsp_api.list_lsps(node_pubkey).await
2881}
2882
2883async fn get_lsp_by_id(
2884 persister: Arc<SqliteStorage>,
2885 lsp_api: Arc<dyn LspAPI>,
2886 lsp_id: &str,
2887) -> SdkResult<Option<LspInformation>> {
2888 Ok(get_lsps(persister, lsp_api)
2889 .await?
2890 .into_iter()
2891 .find(|lsp| lsp.id.as_str() == lsp_id))
2892}
2893
2894async fn get_notification_lsps(
2897 persister: Arc<SqliteStorage>,
2898 lsp_api: Arc<dyn LspAPI>,
2899 node_api: Arc<dyn NodeAPI>,
2900) -> SdkResult<Vec<LspInformation>> {
2901 let node_pubkey = persister
2902 .get_node_state()?
2903 .ok_or(SdkError::generic("Node info not found"))?
2904 .id;
2905 let mut open_peers = None;
2906
2907 let mut notification_lsps = vec![];
2908 for lsp in lsp_api.list_used_lsps(node_pubkey).await? {
2909 match !lsp.opening_fee_params_list.values.is_empty() {
2910 true => {
2911 notification_lsps.push(lsp);
2914 }
2915 false => {
2916 let lsp_pubkey = hex::decode(&lsp.pubkey)
2918 .map_err(|e| anyhow!("Failed decode lsp pubkey: {e}"))?;
2919 let open_peers = match &open_peers {
2920 Some(open_peers) => open_peers,
2921 None => {
2922 open_peers = Some(node_api.get_open_peers().await?);
2923 open_peers.as_ref().unwrap()
2924 }
2925 };
2926 let has_active_channel_to_lsp = open_peers.contains(&lsp_pubkey);
2927 if has_active_channel_to_lsp {
2928 notification_lsps.push(lsp);
2929 }
2930 }
2931 }
2932 }
2933 Ok(notification_lsps)
2934}
2935
2936#[cfg(test)]
2937pub(crate) mod tests {
2938 use std::collections::HashMap;
2939 use std::sync::Arc;
2940
2941 use anyhow::{anyhow, Result};
2942 use regex::Regex;
2943 use reqwest::Url;
2944 use sdk_common::prelude::Rate;
2945
2946 use crate::breez_services::{BreezServices, BreezServicesBuilder};
2947 use crate::models::{LnPaymentDetails, NodeState, Payment, PaymentDetails, PaymentTypeFilter};
2948 use crate::node_api::NodeAPI;
2949 use crate::persist::cache::NodeStateStorage;
2950 use crate::persist::swap::SwapStorage;
2951 use crate::test_utils::*;
2952 use crate::*;
2953
2954 use super::{PaymentReceiver, Receiver};
2955
2956 #[tokio::test]
2957 async fn test_node_state() -> Result<()> {
2958 let dummy_node_state = get_dummy_node_state();
2962
2963 let lnurl_metadata = "{'key': 'sample-metadata-val'}";
2964 let test_ln_address = "test@ln-address.com";
2965 let test_lnurl_withdraw_endpoint = "https://test.endpoint.lnurl-w";
2966 let sa = SuccessActionProcessed::Message {
2967 data: MessageSuccessActionData {
2968 message: "test message".into(),
2969 },
2970 };
2971
2972 let payment_hash_lnurl_withdraw = "2222";
2973 let payment_hash_with_lnurl_success_action = "3333";
2974 let payment_hash_swap: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8];
2975 let swap_info = SwapInfo {
2976 bitcoin_address: "123".to_string(),
2977 created_at: 12345678,
2978 lock_height: 654321,
2979 payment_hash: payment_hash_swap.clone(),
2980 preimage: vec![],
2981 private_key: vec![],
2982 public_key: vec![],
2983 swapper_public_key: vec![],
2984 script: vec![],
2985 bolt11: Some("312".into()),
2986 paid_msat: 1000,
2987 confirmed_sats: 1,
2988 unconfirmed_sats: 0,
2989 total_incoming_txs: 1,
2990 status: SwapStatus::Refundable,
2991 refund_tx_ids: vec![],
2992 unconfirmed_tx_ids: vec![],
2993 confirmed_tx_ids: vec![],
2994 min_allowed_deposit: 5_000,
2995 max_allowed_deposit: 1_000_000,
2996 max_swapper_payable: 2_000_000,
2997 last_redeem_error: None,
2998 channel_opening_fees: Some(OpeningFeeParams {
2999 min_msat: 5_000_000,
3000 proportional: 50,
3001 valid_until: "date".to_string(),
3002 max_idle_time: 12345,
3003 max_client_to_self_delay: 234,
3004 promise: "promise".to_string(),
3005 }),
3006 confirmed_at: Some(555),
3007 };
3008 let payment_hash_rev_swap: Vec<u8> = vec![8, 7, 6, 5, 4, 3, 2, 1];
3009 let preimage_rev_swap: Vec<u8> = vec![6, 6, 6, 6];
3010 let full_ref_swap_info = FullReverseSwapInfo {
3011 id: "rev_swap_id".to_string(),
3012 created_at_block_height: 0,
3013 preimage: preimage_rev_swap.clone(),
3014 private_key: vec![],
3015 claim_pubkey: "claim_pubkey".to_string(),
3016 timeout_block_height: 600_000,
3017 invoice: "645".to_string(),
3018 redeem_script: "redeem_script".to_string(),
3019 onchain_amount_sat: 250,
3020 sat_per_vbyte: Some(50),
3021 receive_amount_sat: None,
3022 cache: ReverseSwapInfoCached {
3023 status: ReverseSwapStatus::CompletedConfirmed,
3024 lockup_txid: Some("lockup_txid".to_string()),
3025 claim_txid: Some("claim_txid".to_string()),
3026 },
3027 };
3028 let rev_swap_info = ReverseSwapInfo {
3029 id: "rev_swap_id".to_string(),
3030 claim_pubkey: "claim_pubkey".to_string(),
3031 lockup_txid: Some("lockup_txid".to_string()),
3032 claim_txid: Some("claim_txid".to_string()),
3033 onchain_amount_sat: 250,
3034 status: ReverseSwapStatus::CompletedConfirmed,
3035 };
3036 let dummy_transactions = vec![
3037 Payment {
3038 id: "1111".to_string(),
3039 payment_type: PaymentType::Received,
3040 payment_time: 100000,
3041 amount_msat: 10,
3042 fee_msat: 0,
3043 status: PaymentStatus::Complete,
3044 error: None,
3045 description: Some("test receive".to_string()),
3046 details: PaymentDetails::Ln {
3047 data: LnPaymentDetails {
3048 payment_hash: "1111".to_string(),
3049 label: "".to_string(),
3050 destination_pubkey: "1111".to_string(),
3051 payment_preimage: "2222".to_string(),
3052 keysend: false,
3053 bolt11: "1111".to_string(),
3054 lnurl_success_action: None,
3055 lnurl_pay_domain: None,
3056 lnurl_pay_comment: None,
3057 lnurl_metadata: None,
3058 ln_address: None,
3059 lnurl_withdraw_endpoint: None,
3060 swap_info: None,
3061 reverse_swap_info: None,
3062 pending_expiration_block: None,
3063 open_channel_bolt11: None,
3064 },
3065 },
3066 metadata: None,
3067 },
3068 Payment {
3069 id: payment_hash_lnurl_withdraw.to_string(),
3070 payment_type: PaymentType::Received,
3071 payment_time: 150000,
3072 amount_msat: 10,
3073 fee_msat: 0,
3074 status: PaymentStatus::Complete,
3075 error: None,
3076 description: Some("test lnurl-withdraw receive".to_string()),
3077 details: PaymentDetails::Ln {
3078 data: LnPaymentDetails {
3079 payment_hash: payment_hash_lnurl_withdraw.to_string(),
3080 label: "".to_string(),
3081 destination_pubkey: "1111".to_string(),
3082 payment_preimage: "3333".to_string(),
3083 keysend: false,
3084 bolt11: "1111".to_string(),
3085 lnurl_success_action: None,
3086 lnurl_pay_domain: None,
3087 lnurl_pay_comment: None,
3088 lnurl_metadata: None,
3089 ln_address: None,
3090 lnurl_withdraw_endpoint: Some(test_lnurl_withdraw_endpoint.to_string()),
3091 swap_info: None,
3092 reverse_swap_info: None,
3093 pending_expiration_block: None,
3094 open_channel_bolt11: None,
3095 },
3096 },
3097 metadata: None,
3098 },
3099 Payment {
3100 id: payment_hash_with_lnurl_success_action.to_string(),
3101 payment_type: PaymentType::Sent,
3102 payment_time: 200000,
3103 amount_msat: 8,
3104 fee_msat: 2,
3105 status: PaymentStatus::Complete,
3106 error: None,
3107 description: Some("test payment".to_string()),
3108 details: PaymentDetails::Ln {
3109 data: LnPaymentDetails {
3110 payment_hash: payment_hash_with_lnurl_success_action.to_string(),
3111 label: "".to_string(),
3112 destination_pubkey: "123".to_string(),
3113 payment_preimage: "4444".to_string(),
3114 keysend: false,
3115 bolt11: "123".to_string(),
3116 lnurl_success_action: Some(sa.clone()),
3117 lnurl_pay_domain: None,
3118 lnurl_pay_comment: None,
3119 lnurl_metadata: Some(lnurl_metadata.to_string()),
3120 ln_address: Some(test_ln_address.to_string()),
3121 lnurl_withdraw_endpoint: None,
3122 swap_info: None,
3123 reverse_swap_info: None,
3124 pending_expiration_block: None,
3125 open_channel_bolt11: None,
3126 },
3127 },
3128 metadata: None,
3129 },
3130 Payment {
3131 id: hex::encode(payment_hash_swap.clone()),
3132 payment_type: PaymentType::Received,
3133 payment_time: 250000,
3134 amount_msat: 1_000,
3135 fee_msat: 0,
3136 status: PaymentStatus::Complete,
3137 error: None,
3138 description: Some("test receive".to_string()),
3139 details: PaymentDetails::Ln {
3140 data: LnPaymentDetails {
3141 payment_hash: hex::encode(payment_hash_swap),
3142 label: "".to_string(),
3143 destination_pubkey: "321".to_string(),
3144 payment_preimage: "5555".to_string(),
3145 keysend: false,
3146 bolt11: "312".to_string(),
3147 lnurl_success_action: None,
3148 lnurl_pay_domain: None,
3149 lnurl_pay_comment: None,
3150 lnurl_metadata: None,
3151 ln_address: None,
3152 lnurl_withdraw_endpoint: None,
3153 swap_info: Some(swap_info.clone()),
3154 reverse_swap_info: None,
3155 pending_expiration_block: None,
3156 open_channel_bolt11: None,
3157 },
3158 },
3159 metadata: None,
3160 },
3161 Payment {
3162 id: hex::encode(payment_hash_rev_swap.clone()),
3163 payment_type: PaymentType::Sent,
3164 payment_time: 300000,
3165 amount_msat: 50_000_000,
3166 fee_msat: 2_000,
3167 status: PaymentStatus::Complete,
3168 error: None,
3169 description: Some("test send onchain".to_string()),
3170 details: PaymentDetails::Ln {
3171 data: LnPaymentDetails {
3172 payment_hash: hex::encode(payment_hash_rev_swap),
3173 label: "".to_string(),
3174 destination_pubkey: "321".to_string(),
3175 payment_preimage: hex::encode(preimage_rev_swap),
3176 keysend: false,
3177 bolt11: "312".to_string(),
3178 lnurl_success_action: None,
3179 lnurl_metadata: None,
3180 lnurl_pay_domain: None,
3181 lnurl_pay_comment: None,
3182 ln_address: None,
3183 lnurl_withdraw_endpoint: None,
3184 swap_info: None,
3185 reverse_swap_info: Some(rev_swap_info.clone()),
3186 pending_expiration_block: None,
3187 open_channel_bolt11: None,
3188 },
3189 },
3190 metadata: None,
3191 },
3192 ];
3193 let node_api = Arc::new(MockNodeAPI::new(dummy_node_state.clone()));
3194
3195 let test_config = create_test_config();
3196 let persister = Arc::new(create_test_persister(test_config.clone()));
3197 persister.init()?;
3198 persister.insert_or_update_payments(&dummy_transactions, false)?;
3199 persister.insert_payment_external_info(
3200 payment_hash_with_lnurl_success_action,
3201 PaymentExternalInfo {
3202 lnurl_pay_success_action: Some(sa.clone()),
3203 lnurl_pay_domain: None,
3204 lnurl_pay_comment: None,
3205 lnurl_metadata: Some(lnurl_metadata.to_string()),
3206 ln_address: Some(test_ln_address.to_string()),
3207 lnurl_withdraw_endpoint: None,
3208 attempted_amount_msat: None,
3209 attempted_error: None,
3210 },
3211 )?;
3212 persister.insert_payment_external_info(
3213 payment_hash_lnurl_withdraw,
3214 PaymentExternalInfo {
3215 lnurl_pay_success_action: None,
3216 lnurl_pay_domain: None,
3217 lnurl_pay_comment: None,
3218 lnurl_metadata: None,
3219 ln_address: None,
3220 lnurl_withdraw_endpoint: Some(test_lnurl_withdraw_endpoint.to_string()),
3221 attempted_amount_msat: None,
3222 attempted_error: None,
3223 },
3224 )?;
3225 persister.insert_swap(&swap_info)?;
3226 persister.update_swap_bolt11(
3227 swap_info.bitcoin_address.clone(),
3228 swap_info.bolt11.clone().unwrap(),
3229 )?;
3230 persister.insert_reverse_swap(&full_ref_swap_info)?;
3231 persister
3232 .update_reverse_swap_status("rev_swap_id", &ReverseSwapStatus::CompletedConfirmed)?;
3233 persister
3234 .update_reverse_swap_lockup_txid("rev_swap_id", Some("lockup_txid".to_string()))?;
3235 persister.update_reverse_swap_claim_txid("rev_swap_id", Some("claim_txid".to_string()))?;
3236
3237 let mut builder = BreezServicesBuilder::new(test_config.clone());
3238 let breez_services = builder
3239 .lsp_api(Arc::new(MockBreezServer {}))
3240 .fiat_api(Arc::new(MockBreezServer {}))
3241 .node_api(node_api)
3242 .persister(persister)
3243 .backup_transport(Arc::new(MockBackupTransport::new()))
3244 .build(None, None)
3245 .await?;
3246
3247 breez_services.sync().await?;
3248 let fetched_state = breez_services.node_info()?;
3249 assert_eq!(fetched_state, dummy_node_state);
3250
3251 let all = breez_services
3252 .list_payments(ListPaymentsRequest::default())
3253 .await?;
3254 let mut cloned = all.clone();
3255
3256 cloned.reverse();
3258 assert_eq!(dummy_transactions, cloned);
3259
3260 let received = breez_services
3261 .list_payments(ListPaymentsRequest {
3262 filters: Some(vec![PaymentTypeFilter::Received]),
3263 ..Default::default()
3264 })
3265 .await?;
3266 assert_eq!(
3267 received,
3268 vec![cloned[3].clone(), cloned[1].clone(), cloned[0].clone()]
3269 );
3270
3271 let sent = breez_services
3272 .list_payments(ListPaymentsRequest {
3273 filters: Some(vec![
3274 PaymentTypeFilter::Sent,
3275 PaymentTypeFilter::ClosedChannel,
3276 ]),
3277 ..Default::default()
3278 })
3279 .await?;
3280 assert_eq!(sent, vec![cloned[4].clone(), cloned[2].clone()]);
3281 assert!(matches!(
3282 &sent[1].details,
3283 PaymentDetails::Ln {data: LnPaymentDetails {lnurl_success_action, ..}}
3284 if lnurl_success_action == &Some(sa)));
3285 assert!(matches!(
3286 &sent[1].details,
3287 PaymentDetails::Ln {data: LnPaymentDetails {lnurl_pay_domain, ln_address, ..}}
3288 if lnurl_pay_domain.is_none() && ln_address == &Some(test_ln_address.to_string())));
3289 assert!(matches!(
3290 &received[1].details,
3291 PaymentDetails::Ln {data: LnPaymentDetails {lnurl_withdraw_endpoint, ..}}
3292 if lnurl_withdraw_endpoint == &Some(test_lnurl_withdraw_endpoint.to_string())));
3293 assert!(matches!(
3294 &received[0].details,
3295 PaymentDetails::Ln {data: LnPaymentDetails {swap_info: swap, ..}}
3296 if swap == &Some(swap_info)));
3297 assert!(matches!(
3298 &sent[0].details,
3299 PaymentDetails::Ln {data: LnPaymentDetails {reverse_swap_info: rev_swap, ..}}
3300 if rev_swap == &Some(rev_swap_info)));
3301
3302 Ok(())
3303 }
3304
3305 #[tokio::test]
3306 async fn test_receive_with_open_channel() -> Result<()> {
3307 let config = create_test_config();
3308 let persister = Arc::new(create_test_persister(config.clone()));
3309 persister.init().unwrap();
3310
3311 let dummy_node_state = get_dummy_node_state();
3312
3313 let node_api = Arc::new(MockNodeAPI::new(dummy_node_state.clone()));
3314
3315 let breez_server = Arc::new(MockBreezServer {});
3316 persister.set_lsp(breez_server.lsp_id(), None).unwrap();
3317 persister.set_node_state(&dummy_node_state).unwrap();
3318
3319 let receiver: Arc<dyn Receiver> = Arc::new(PaymentReceiver {
3320 config,
3321 node_api,
3322 persister,
3323 lsp: breez_server.clone(),
3324 });
3325 let ln_invoice = receiver
3326 .receive_payment(ReceivePaymentRequest {
3327 amount_msat: 3_000_000,
3328 description: "should populate lsp hints".to_string(),
3329 use_description_hash: Some(false),
3330 ..Default::default()
3331 })
3332 .await?
3333 .ln_invoice;
3334 assert_eq!(ln_invoice.routing_hints[0].hops.len(), 1);
3335 let lsp_hop = &ln_invoice.routing_hints[0].hops[0];
3336 assert_eq!(lsp_hop.src_node_id, breez_server.clone().lsp_pub_key());
3337 assert_eq!(lsp_hop.short_channel_id, "1x0x0");
3338 Ok(())
3339 }
3340
3341 #[tokio::test]
3342 async fn test_list_lsps() -> Result<()> {
3343 let storage_path = format!("{}/storage.sql", get_test_working_dir());
3344 std::fs::remove_file(storage_path).ok();
3345
3346 let breez_services = breez_services()
3347 .await
3348 .map_err(|e| anyhow!("Failed to get the BreezServices: {e}"))?;
3349 breez_services.sync().await?;
3350
3351 let node_pubkey = breez_services.node_info()?.id;
3352 let lsps = breez_services.lsp_api.list_lsps(node_pubkey).await?;
3353 assert_eq!(lsps.len(), 1);
3354
3355 Ok(())
3356 }
3357
3358 #[tokio::test]
3359 async fn test_fetch_rates() -> Result<(), Box<dyn std::error::Error>> {
3360 let breez_services = breez_services().await?;
3361 breez_services.sync().await?;
3362
3363 let rates = breez_services.fiat_api.fetch_fiat_rates().await?;
3364 assert_eq!(rates.len(), 1);
3365 assert_eq!(
3366 rates[0],
3367 Rate {
3368 coin: "USD".to_string(),
3369 value: 20_000.00,
3370 }
3371 );
3372
3373 Ok(())
3374 }
3375
3376 #[tokio::test]
3377 async fn test_buy_bitcoin_with_moonpay() -> Result<(), Box<dyn std::error::Error>> {
3378 let mock_rest_client = MockRestClient::new();
3379 mock_rest_client.add_response(MockResponse::new(200, "800000".to_string()));
3380 let rest_client: Arc<dyn RestClient> = Arc::new(mock_rest_client);
3381
3382 let breez_services = breez_services_with(None, Some(rest_client.clone()), vec![]).await?;
3383 breez_services.sync().await?;
3384
3385 let moonpay_url = breez_services
3386 .buy_bitcoin(BuyBitcoinRequest {
3387 provider: BuyBitcoinProvider::Moonpay,
3388 opening_fee_params: None,
3389 redirect_url: None,
3390 })
3391 .await?
3392 .url;
3393 let parsed = Url::parse(&moonpay_url)?;
3394 let query_pairs = parsed.query_pairs().into_owned().collect::<HashMap<_, _>>();
3395
3396 assert_eq!(parsed.host_str(), Some("mock.moonpay"));
3397 assert_eq!(parsed.path(), "/");
3398
3399 let wallet_address =
3400 parse_with_rest_client(rest_client.as_ref(), query_pairs.get("wa").unwrap(), None)
3401 .await?;
3402 assert!(matches!(wallet_address, InputType::BitcoinAddress { .. }));
3403
3404 let max_amount = query_pairs.get("ma").unwrap();
3405 assert!(Regex::new(r"^\d+\.\d{8}$").unwrap().is_match(max_amount));
3406
3407 Ok(())
3408 }
3409
3410 pub(crate) async fn breez_services() -> Result<Arc<BreezServices>> {
3412 breez_services_with(None, None, vec![]).await
3413 }
3414
3415 pub(crate) async fn breez_services_with(
3417 node_api: Option<Arc<dyn NodeAPI>>,
3418 rest_client: Option<Arc<dyn RestClient>>,
3419 known_payments: Vec<Payment>,
3420 ) -> Result<Arc<BreezServices>> {
3421 let node_api =
3422 node_api.unwrap_or_else(|| Arc::new(MockNodeAPI::new(get_dummy_node_state())));
3423 let rest_client: Arc<dyn RestClient> =
3424 rest_client.unwrap_or_else(|| Arc::new(MockRestClient::new()));
3425
3426 let test_config = create_test_config();
3427 let persister = Arc::new(create_test_persister(test_config.clone()));
3428 persister.init()?;
3429 persister.insert_or_update_payments(&known_payments, false)?;
3430 persister.set_lsp(MockBreezServer {}.lsp_id(), None)?;
3431
3432 let mut builder = BreezServicesBuilder::new(test_config.clone());
3433 let breez_services = builder
3434 .lsp_api(Arc::new(MockBreezServer {}))
3435 .fiat_api(Arc::new(MockBreezServer {}))
3436 .taproot_swapper_api(Arc::new(MockBreezServer {}))
3437 .reverse_swap_service_api(Arc::new(MockReverseSwapperAPI {}))
3438 .buy_bitcoin_api(Arc::new(MockBuyBitcoinService {}))
3439 .persister(persister)
3440 .node_api(node_api)
3441 .rest_client(rest_client)
3442 .backup_transport(Arc::new(MockBackupTransport::new()))
3443 .build(None, None)
3444 .await?;
3445
3446 Ok(breez_services)
3447 }
3448
3449 pub(crate) fn get_dummy_node_state() -> NodeState {
3451 NodeState {
3452 id: "tx1".to_string(),
3453 block_height: 1,
3454 channels_balance_msat: 100,
3455 onchain_balance_msat: 1_000,
3456 pending_onchain_balance_msat: 100,
3457 utxos: vec![],
3458 max_payable_msat: 95,
3459 max_receivable_msat: 4_000_000_000,
3460 max_single_payment_amount_msat: 1_000,
3461 max_chan_reserve_msats: 0,
3462 connected_peers: vec!["1111".to_string()],
3463 max_receivable_single_payment_amount_msat: 2_000,
3464 total_inbound_liquidity_msats: 10_000,
3465 }
3466 }
3467}