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