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