breez_sdk_core/
breez_services.rs

1use std::collections::HashMap;
2use std::fs::OpenOptions;
3use std::io::Write;
4use std::str::FromStr;
5use std::sync::Arc;
6use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
7
8use anyhow::{anyhow, Result};
9use bip39::*;
10use bitcoin::hashes::hex::ToHex;
11use bitcoin::hashes::{sha256, Hash};
12use bitcoin::util::bip32::ChildNumber;
13use chrono::Local;
14use futures::{StreamExt, TryFutureExt};
15use log::{LevelFilter, Metadata, Record};
16use sdk_common::grpc;
17use sdk_common::prelude::*;
18use serde::Serialize;
19use serde_json::{json, Value};
20use strum_macros::EnumString;
21use tokio::sync::{mpsc, watch, Mutex};
22use tokio::time::{sleep, MissedTickBehavior};
23
24use crate::backup::{BackupRequest, BackupTransport, BackupWatcher};
25use crate::buy::{BuyBitcoinApi, BuyBitcoinService};
26use crate::chain::{
27    ChainService, Outspend, RecommendedFees, RedundantChainService, RedundantChainServiceTrait,
28    DEFAULT_MEMPOOL_SPACE_URL,
29};
30use crate::error::{
31    ConnectError, ReceiveOnchainError, ReceiveOnchainResult, ReceivePaymentError,
32    RedeemOnchainResult, SdkError, SdkResult, SendOnchainError, SendPaymentError,
33};
34use crate::lnurl::auth::SdkLnurlAuthSigner;
35use crate::lnurl::pay::*;
36use crate::lsp::LspInformation;
37use crate::models::{
38    sanitize::*, ChannelState, ClosedChannelPaymentDetails, Config, EnvironmentType, LspAPI,
39    NodeState, Payment, PaymentDetails, PaymentType, ReverseSwapPairInfo, ReverseSwapServiceAPI,
40    SwapInfo, SwapperAPI, INVOICE_PAYMENT_FEE_EXPIRY_SECONDS,
41};
42use crate::node_api::{CreateInvoiceRequest, NodeAPI};
43use crate::persist::cache::NodeStateStorage;
44use crate::persist::db::SqliteStorage;
45use crate::persist::swap::SwapStorage;
46use crate::persist::transactions::PaymentStorage;
47use crate::swap_in::{BTCReceiveSwap, BTCReceiveSwapParameters, TaprootSwapperAPI};
48use crate::swap_out::boltzswap::BoltzApi;
49use crate::swap_out::reverseswap::BTCSendSwap;
50use crate::*;
51
52pub type BreezServicesResult<T, E = ConnectError> = Result<T, E>;
53
54/// Trait that can be used to react to various [BreezEvent]s emitted by the SDK.
55pub trait EventListener: Send + Sync {
56    fn on_event(&self, e: BreezEvent);
57}
58
59/// Event emitted by the SDK. To listen for and react to these events, use an [EventListener] when
60/// initializing the [BreezServices].
61#[derive(Clone, Debug, PartialEq)]
62#[allow(clippy::large_enum_variant)]
63pub enum BreezEvent {
64    /// Indicates that a new block has just been found
65    NewBlock { block: u32 },
66    /// Indicates that a new invoice has just been paid
67    InvoicePaid { details: InvoicePaidDetails },
68    /// Indicates that the local SDK state has just been sync-ed with the remote components
69    Synced,
70    /// Indicates that an outgoing payment has been completed successfully
71    PaymentSucceed { details: Payment },
72    /// Indicates that an outgoing payment has been failed to complete
73    PaymentFailed { details: PaymentFailedData },
74    /// Indicates that the backup process has just started
75    BackupStarted,
76    /// Indicates that the backup process has just finished successfully
77    BackupSucceeded,
78    /// Indicates that the backup process has just failed
79    BackupFailed { details: BackupFailedData },
80    /// Indicates that a reverse swap has been updated which may also
81    /// include a status change
82    ReverseSwapUpdated { details: ReverseSwapInfo },
83    /// Indicates that a swap has been updated which may also
84    /// include a status change
85    SwapUpdated { details: SwapInfo },
86}
87
88#[derive(Clone, Debug, PartialEq)]
89pub struct BackupFailedData {
90    pub error: String,
91}
92
93#[derive(Clone, Debug, PartialEq)]
94pub struct PaymentFailedData {
95    pub error: String,
96    pub node_id: String,
97    pub invoice: Option<LNInvoice>,
98    pub label: Option<String>,
99}
100
101/// Details of an invoice that has been paid, included as payload in an emitted [BreezEvent]
102#[derive(Clone, Debug, PartialEq)]
103pub struct InvoicePaidDetails {
104    pub payment_hash: String,
105    pub bolt11: String,
106    pub payment: Option<Payment>,
107}
108
109pub trait LogStream: Send + Sync {
110    fn log(&self, l: LogEntry);
111}
112
113/// Request to sign a message with the node's private key.
114#[derive(Clone, Debug, PartialEq)]
115pub struct SignMessageRequest {
116    /// The message to be signed by the node's private key.
117    pub message: String,
118}
119
120/// Response to a [SignMessageRequest].
121#[derive(Clone, Debug, PartialEq)]
122pub struct SignMessageResponse {
123    /// The signature that covers the message of SignMessageRequest. Zbase
124    /// encoded.
125    pub signature: String,
126}
127
128/// Request to check a message was signed by a specific node id.
129#[derive(Clone, Debug, PartialEq)]
130pub struct CheckMessageRequest {
131    /// The message that was signed.
132    pub message: String,
133    /// The public key of the node that signed the message.
134    pub pubkey: String,
135    /// The zbase encoded signature to verify.
136    pub signature: String,
137}
138
139/// Response to a [CheckMessageRequest]
140#[derive(Clone, Debug, PartialEq)]
141pub struct CheckMessageResponse {
142    /// Boolean value indicating whether the signature covers the message and
143    /// was signed by the given pubkey.
144    pub is_valid: bool,
145}
146
147#[derive(Clone, PartialEq, EnumString, Serialize)]
148enum DevCommand {
149    /// Generates diagnostic data report.
150    #[strum(serialize = "generatediagnosticdata")]
151    GenerateDiagnosticData,
152}
153
154/// BreezServices is a facade and the single entry point for the SDK.
155pub struct BreezServices {
156    config: Config,
157    started: Mutex<bool>,
158    node_api: Arc<dyn NodeAPI>,
159    lsp_api: Arc<dyn LspAPI>,
160    fiat_api: Arc<dyn FiatAPI>,
161    buy_bitcoin_api: Arc<dyn BuyBitcoinApi>,
162    support_api: Arc<dyn SupportAPI>,
163    chain_service: Arc<dyn ChainService>,
164    persister: Arc<SqliteStorage>,
165    rest_client: Arc<dyn RestClient>,
166    payment_receiver: Arc<PaymentReceiver>,
167    btc_receive_swapper: Arc<BTCReceiveSwap>,
168    btc_send_swapper: Arc<BTCSendSwap>,
169    event_listener: Option<Box<dyn EventListener>>,
170    backup_watcher: Arc<BackupWatcher>,
171    shutdown_sender: watch::Sender<()>,
172}
173
174impl BreezServices {
175    /// `connect` initializes the SDK services, schedules the node to run in the cloud and
176    /// runs the signer. This must be called in order to start communicating with the node.
177    ///
178    /// Once you are finished using the SDK services, call [BreezServices::disconnect] to
179    /// free up the resources which are currently in use. This is especially useful in cases
180    /// where the SDK has to be re-instantiated, for example, when you need to change the
181    /// mnemonic and/or configuration.
182    ///
183    /// # Arguments
184    ///
185    /// * `req` - The connect request containing the `config` SDK configuration and `seed` node
186    ///   private key, typically derived from the mnemonic. When using a new `invite_code`,
187    ///   the seed should be derived from a new random mnemonic. When re-using an `invite_code`,
188    ///   the same mnemonic should be used as when the `invite_code` was first used.
189    /// * `event_listener` - Listener to SDK events
190    ///
191    pub async fn connect(
192        req: ConnectRequest,
193        event_listener: Box<dyn EventListener>,
194    ) -> BreezServicesResult<Arc<BreezServices>> {
195        let (sdk_version, sdk_git_hash) = Self::get_sdk_version();
196        info!("SDK v{sdk_version} ({sdk_git_hash})");
197        let start = Instant::now();
198        let services = BreezServicesBuilder::new(req.config)
199            .seed(req.seed)
200            .build(req.restore_only, Some(event_listener))
201            .await?;
202        services.start().await?;
203        let connect_duration = start.elapsed();
204        info!("SDK connected in: {connect_duration:?}");
205        Ok(services)
206    }
207
208    fn get_sdk_version() -> (&'static str, &'static str) {
209        let sdk_version = option_env!("CARGO_PKG_VERSION").unwrap_or_default();
210        let sdk_git_hash = option_env!("SDK_GIT_HASH").unwrap_or_default();
211        (sdk_version, sdk_git_hash)
212    }
213
214    /// Internal utility method that starts the BreezServices background tasks for this instance.
215    ///
216    /// It should be called once right after creating [BreezServices], since it is essential for the
217    /// communicating with the node.
218    ///
219    /// It should be called only once when the app is started, regardless whether the app is sent to
220    /// background and back.
221    async fn start(self: &Arc<BreezServices>) -> BreezServicesResult<()> {
222        let mut started = self.started.lock().await;
223        ensure_sdk!(
224            !*started,
225            ConnectError::Generic {
226                err: "BreezServices already started".into()
227            }
228        );
229
230        let start = Instant::now();
231        self.start_background_tasks().await?;
232        let start_duration = start.elapsed();
233        info!("SDK initialized in: {start_duration:?}");
234        *started = true;
235        Ok(())
236    }
237
238    /// Trigger the stopping of BreezServices background threads for this instance.
239    pub async fn disconnect(&self) -> SdkResult<()> {
240        let mut started = self.started.lock().await;
241        ensure_sdk!(
242            *started,
243            SdkError::Generic {
244                err: "BreezServices is not running".into(),
245            }
246        );
247        self.shutdown_sender
248            .send(())
249            .map_err(|e| SdkError::Generic {
250                err: format!("Shutdown failed: {e}"),
251            })?;
252        self.shutdown_sender.closed().await;
253        *started = false;
254        Ok(())
255    }
256
257    /// Configure the node
258    ///
259    /// This calls [NodeAPI::configure_node] to make changes to the active node's configuration.
260    /// Configuring the [ConfigureNodeRequest::close_to_address] only needs to be done one time
261    /// when registering the node or when the close to address needs to be changed. Otherwise, it is
262    /// stored by the node and used when necessary.
263    pub async fn configure_node(&self, req: ConfigureNodeRequest) -> SdkResult<()> {
264        Ok(self.node_api.configure_node(req.close_to_address).await?)
265    }
266
267    /// Pay a bolt11 invoice
268    ///
269    /// Calling `send_payment` ensures that the payment is not already completed; if so, it will result in an error.
270    /// If the invoice doesn't specify an amount, the amount is taken from the `amount_msat` arg.
271    pub async fn send_payment(
272        &self,
273        req: SendPaymentRequest,
274    ) -> Result<SendPaymentResponse, SendPaymentError> {
275        let parsed_invoice = parse_invoice(req.bolt11.as_str())?;
276        let invoice_expiration = parsed_invoice.timestamp + parsed_invoice.expiry;
277        let current_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
278        if invoice_expiration < current_time {
279            return Err(SendPaymentError::InvoiceExpired {
280                err: format!("Invoice expired at {invoice_expiration}"),
281            });
282        }
283        let invoice_amount_msat = parsed_invoice.amount_msat.unwrap_or_default();
284        let provided_amount_msat = req.amount_msat.unwrap_or_default();
285
286        // Valid the invoice network against the config network
287        validate_network(parsed_invoice.clone(), self.config.network)?;
288
289        let amount_msat = match (provided_amount_msat, invoice_amount_msat) {
290            (0, 0) => {
291                return Err(SendPaymentError::InvalidAmount {
292                    err: "Amount must be provided when paying a zero invoice".into(),
293                })
294            }
295            (0, amount_msat) => amount_msat,
296            (amount_msat, 0) => amount_msat,
297            (_amount_1, _amount_2) => {
298                return Err(SendPaymentError::InvalidAmount {
299                    err: "Amount should not be provided when paying a non zero invoice".into(),
300                })
301            }
302        };
303
304        if self
305            .persister
306            .get_completed_payment_by_hash(&parsed_invoice.payment_hash)?
307            .is_some()
308        {
309            return Err(SendPaymentError::AlreadyPaid);
310        }
311
312        // If there is an lsp, the invoice route hint does not contain the
313        // lsp in the hint, and trampoline payments are requested, attempt a
314        // trampoline payment.
315        let maybe_trampoline_id = self.get_trampoline_id(&req, &parsed_invoice)?;
316
317        self.persist_pending_payment(&parsed_invoice, amount_msat, req.label.clone())?;
318
319        // If trampoline is an option, try trampoline first.
320        let trampoline_result = if let Some(trampoline_id) = maybe_trampoline_id {
321            debug!("attempting trampoline payment");
322            match self
323                .node_api
324                .send_trampoline_payment(
325                    parsed_invoice.bolt11.clone(),
326                    amount_msat,
327                    req.label.clone(),
328                    trampoline_id,
329                )
330                .await
331            {
332                Ok(res) => Some(res),
333                Err(e) => {
334                    if e.to_string().contains("missing balance") {
335                        debug!("trampoline payment failed due to insufficient balance: {e:?}");
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        // If trampoline failed or didn't happen, fall back to regular payment.
351        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 trampoline is turned off, return immediately
384        if !req.use_trampoline {
385            return Ok(None);
386        }
387
388        // Get the persisted LSP id. If no LSP, return early.
389        let lsp_pubkey = match self.persister.get_lsp_pubkey()? {
390            Some(lsp_pubkey) => lsp_pubkey,
391            None => return Ok(None),
392        };
393
394        // If the LSP is in the routing hint, don't use trampoline, but rather
395        // pay directly to the destination.
396        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        // If ended up here, this payment will attempt trampoline.
406        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    /// Pay directly to a node id using keysend
414    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    /// Second step of LNURL-pay. The first step is `parse()`, which also validates the LNURL destination
435    /// and generates the `LnUrlPayRequest` payload needed here.
436    ///
437    /// This call will validate the `amount_msat` and `comment` parameters of `req` against the parameters
438    /// of the LNURL endpoint (`req_data`). If they match the endpoint requirements, the LNURL payment
439    /// is made.
440    ///
441    /// This method will return an [anyhow::Error] when any validation check fails.
442    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                            // For AES, we decrypt the contents on the fly
494                            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                // Store SA (if available) + LN Address in separate table, associated to payment_hash
520                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    /// Second step of LNURL-withdraw. The first step is `parse()`, which also validates the LNURL destination
545    /// and generates the `LnUrlWithdrawRequest` payload needed here.
546    ///
547    /// This call will validate the given `amount_msat` against the parameters
548    /// of the LNURL endpoint (`data`). If they match the endpoint requirements, the LNURL withdraw
549    /// request is made. A successful result here means the endpoint started the payment.
550    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            // If endpoint was successfully called, store the LNURL-withdraw endpoint URL as metadata linked to the invoice
569            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    /// Third and last step of LNURL-auth. The first step is `parse()`, which also validates the LNURL destination
588    /// and generates the `LnUrlAuthRequestData` payload needed here. The second step is user approval of the auth action.
589    ///
590    /// This call will sign `k1` of the LNURL endpoint (`req_data`) on `secp256k1` using `linkingPrivKey` and DER-encodes the signature.
591    /// If they match the endpoint requirements, the LNURL auth request is made. A successful result here means the client signature is verified.
592    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    /// Creates an bolt11 payment request.
605    /// This also works when the node doesn't have any channels and need inbound liquidity.
606    /// In such case when the invoice is paid a new zero-conf channel will be open by the LSP,
607    /// providing inbound liquidity and the payment will be routed via this new channel.
608    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    /// Report an issue.
616    ///
617    /// Calling `report_issue` with a [ReportIssueRequest] enum param sends an issue report using the Support API.
618    /// - [ReportIssueRequest::PaymentFailure] sends a payment failure report to the Support API
619    ///   using the provided `payment_hash` to lookup the failed payment and the current [NodeState].
620    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    /// Retrieve the decrypted credentials from the node.
644    pub async fn node_credentials(&self) -> SdkResult<Option<NodeCredentials>> {
645        Ok(self.node_api.node_credentials().await?)
646    }
647
648    /// Retrieve the node state from the persistent storage.
649    ///
650    /// Fail if it could not be retrieved or if `None` was found.
651    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    /// Sign given message with the private key of the node id. Returns a zbase
658    /// encoded signature.
659    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    /// Check whether given message was signed by the private key or the given
665    /// pubkey and the signature (zbase encoded) is valid.
666    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    /// Retrieve the node up to date BackupStatus
675    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    /// Force running backup
685    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    /// List payments matching the given filters, as retrieved from persistent storage
701    pub async fn list_payments(&self, req: ListPaymentsRequest) -> SdkResult<Vec<Payment>> {
702        Ok(self.persister.list_payments(req)?)
703    }
704
705    /// Fetch a specific payment by its hash.
706    pub async fn payment_by_hash(&self, hash: String) -> SdkResult<Option<Payment>> {
707        Ok(self.persister.get_payment_by_hash(&hash)?)
708    }
709
710    /// Set the external metadata of a payment as a valid JSON string
711    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    /// Redeem on-chain funds from closed channels to the specified on-chain address, with the given feerate
718    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    /// Fetch live rates of fiat currencies, sorted by name
739    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    /// List all supported fiat currencies for which there is a known exchange rate.
744    /// List is sorted by the canonical name of the currency
745    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    /// List available LSPs that can be selected by the user
753    pub async fn list_lsps(&self) -> SdkResult<Vec<LspInformation>> {
754        self.lsp_api.list_lsps(self.node_info()?.id).await
755    }
756
757    /// Select the LSP to be used and provide inbound liquidity
758    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    /// Get the current LSP's ID
777    pub async fn lsp_id(&self) -> SdkResult<Option<String>> {
778        Ok(self.persister.get_lsp_id()?)
779    }
780
781    /// Convenience method to look up [LspInformation] for a given LSP ID
782    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    /// Gets the fees required to open a channel for a given amount.
787    /// If no channel is needed, returns 0. If a channel is needed, returns the required opening fees.
788    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                // In case we have enough inbound liquidity we return zero fee.
801                true => 0,
802                // Otherwise we need to calculate the fee for opening a new channel.
803                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    /// Close all channels with the current LSP.
814    ///
815    /// Should be called when the user wants to close all the channels.
816    pub async fn close_lsp_channels(&self) -> SdkResult<Vec<String>> {
817        let lsps = self.get_lsps().await?;
818        let mut all_txids = Vec::new();
819        for lsp in lsps {
820            let txids = self.node_api.close_peer_channels(lsp.pubkey).await?;
821            all_txids.extend(txids);
822        }
823        self.sync().await?;
824        Ok(all_txids)
825    }
826
827    /// Onchain receive swap API
828    ///
829    /// Create and start a new swap. A user-selected [OpeningFeeParams] can be optionally set in the argument.
830    /// If set, and the operation requires a new channel, the SDK will use the given fee params.
831    /// The provided [OpeningFeeParams] need to be valid at the time of swap redeeming.
832    ///
833    /// Since we only allow one in-progress swap, this method will return an error if there is currently
834    /// a swap waiting for confirmation to be redeemed and by that complete the swap.
835    /// In such a case, the [BreezServices::in_progress_swap] can be used to query the live swap status.
836    ///
837    /// The returned [SwapInfo] contains the created swap details. The channel opening fees are
838    /// available at [SwapInfo::channel_opening_fees].
839    pub async fn receive_onchain(
840        &self,
841        req: ReceiveOnchainRequest,
842    ) -> ReceiveOnchainResult<SwapInfo> {
843        if let Some(in_progress) = self.in_progress_swap().await? {
844            return Err(ReceiveOnchainError::SwapInProgress{ err:format!(
845                    "A swap was detected for address {}. Use in_progress_swap method to get the current swap state",
846                    in_progress.bitcoin_address
847                )});
848        }
849        let channel_opening_fees = req.opening_fee_params.unwrap_or(
850            self.lsp_info()
851                .await?
852                .cheapest_open_channel_fee(SWAP_PAYMENT_FEE_EXPIRY_SECONDS)?
853                .clone(),
854        );
855
856        let swap_info = self
857            .btc_receive_swapper
858            .create_swap(channel_opening_fees)
859            .await?;
860        if let Some(webhook_url) = self.persister.get_webhook_url()? {
861            let address = &swap_info.bitcoin_address;
862            info!("Registering for onchain tx notification for address {address}");
863            self.register_onchain_tx_notification(address, &webhook_url)
864                .await?;
865        }
866        Ok(swap_info)
867    }
868
869    /// Returns an optional in-progress [SwapInfo].
870    /// A [SwapInfo] is in-progress if it is waiting for confirmation to be redeemed and complete the swap.
871    pub async fn in_progress_swap(&self) -> SdkResult<Option<SwapInfo>> {
872        let tip = self.chain_service.current_tip().await?;
873        self.btc_receive_swapper.rescan_monitored_swaps(tip).await?;
874        let in_progress = self.btc_receive_swapper.list_in_progress_swaps()?;
875        if !in_progress.is_empty() {
876            return Ok(Some(in_progress[0].clone()));
877        }
878        Ok(None)
879    }
880
881    /// Iterate all historical swap addresses and fetch their current status from the blockchain.
882    /// The status is then updated in the persistent storage.
883    pub async fn rescan_swaps(&self) -> SdkResult<()> {
884        let tip = self.chain_service.current_tip().await?;
885        self.btc_receive_swapper.rescan_swaps(tip).await?;
886        Ok(())
887    }
888
889    /// Redeems an individual swap.
890    ///
891    /// To be used only in the context of mobile notifications, where the notification triggers
892    /// an individual redeem.
893    ///
894    /// This is taken care of automatically in the context of typical SDK usage.
895    pub async fn redeem_swap(&self, swap_address: String) -> SdkResult<()> {
896        let tip = self.chain_service.current_tip().await?;
897        self.btc_receive_swapper
898            .rescan_swap(&swap_address, tip)
899            .await?;
900        self.btc_receive_swapper.redeem_swap(swap_address).await?;
901        Ok(())
902    }
903
904    /// Lists current and historical swaps.
905    ///
906    /// Swaps can be filtered based on creation time and status.
907    pub async fn list_swaps(&self, req: ListSwapsRequest) -> SdkResult<Vec<SwapInfo>> {
908        Ok(self.persister.list_swaps(req)?)
909    }
910
911    /// Claims an individual reverse swap.
912    ///
913    /// To be used only in the context of mobile notifications, where the notification triggers
914    /// an individual reverse swap to be claimed.
915    ///
916    /// This is taken care of automatically in the context of typical SDK usage.
917    pub async fn claim_reverse_swap(&self, lockup_address: String) -> SdkResult<()> {
918        Ok(self
919            .btc_send_swapper
920            .claim_reverse_swap(lockup_address)
921            .await?)
922    }
923
924    /// Lookup the reverse swap fees (see [ReverseSwapServiceAPI::fetch_reverse_swap_fees]).
925    ///
926    /// If the request has the `send_amount_sat` set, the returned [ReverseSwapPairInfo] will have
927    /// the total estimated fees for the reverse swap in its `total_estimated_fees`.
928    ///
929    /// If, in addition to that, the request has the `claim_tx_feerate` set as well, then
930    /// - `fees_claim` will have the actual claim transaction fees, instead of an estimate, and
931    /// - `total_estimated_fees` will have the actual total fees for the given parameters
932    ///
933    /// ### Errors
934    ///
935    /// If a `send_amount_sat` is specified in the `req`, but is outside the `min` and `max`,
936    /// this will result in an error. If you are not sure what are the `min` and `max`, please call
937    /// this with `send_amount_sat` as `None` first, then repeat the call with the desired amount.
938    pub async fn fetch_reverse_swap_fees(
939        &self,
940        req: ReverseSwapFeesRequest,
941    ) -> SdkResult<ReverseSwapPairInfo> {
942        let mut res = self.btc_send_swapper.fetch_reverse_swap_fees().await?;
943
944        if let Some(amt) = req.send_amount_sat {
945            ensure_sdk!(amt <= res.max, SdkError::generic("Send amount is too high"));
946            ensure_sdk!(amt >= res.min, SdkError::generic("Send amount is too low"));
947
948            if let Some(claim_tx_feerate) = req.claim_tx_feerate {
949                res.fees_claim = BTCSendSwap::calculate_claim_tx_fee(claim_tx_feerate)?;
950            }
951
952            let service_fee_sat = swap_out::get_service_fee_sat(amt, res.fees_percentage);
953            res.total_fees = Some(service_fee_sat + res.fees_lockup + res.fees_claim);
954        }
955
956        Ok(res)
957    }
958    /// Returns the max amount that can be sent on-chain using the send_onchain method.
959    /// The returned amount is the sum of the max amount that can be sent on each channel
960    /// minus the expected fees.
961    /// This is possible since the route to the swapper node is known in advance and is expected
962    /// to consist of a maximum of 3 hops.
963    async fn max_reverse_swap_amount(&self) -> SdkResult<u64> {
964        // fetch the last hop hints from the swapper
965        let last_hop = self.btc_send_swapper.last_hop_for_payment().await?;
966        info!("max_reverse_swap_amount last_hop={last_hop:?}");
967        // calculate the largest payment we can send over this route using maximum 3 hops
968        // as follows:
969        // User Node -> LSP Node -> Routing Node -> Swapper Node
970        let max_to_pay = self
971            .node_api
972            .max_sendable_amount(
973                Some(
974                    hex::decode(&last_hop.src_node_id).map_err(|e| SdkError::Generic {
975                        err: format!("Failed to decode hex node_id: {e}"),
976                    })?,
977                ),
978                swap_out::reverseswap::MAX_PAYMENT_PATH_HOPS,
979                Some(&last_hop),
980            )
981            .await?;
982
983        // Sum the max amount per channel and return the result
984        let total_msat: u64 = max_to_pay.into_iter().map(|m| m.amount_msat).sum();
985        let total_sat = total_msat / 1000;
986        Ok(total_sat)
987    }
988
989    /// list non-completed expired swaps that should be refunded by calling [BreezServices::refund]
990    pub async fn list_refundables(&self) -> SdkResult<Vec<SwapInfo>> {
991        Ok(self.btc_receive_swapper.list_refundables()?)
992    }
993
994    /// Prepares a refund transaction for a failed/expired swap.
995    ///
996    /// Can optionally be used before [BreezServices::refund] to know how much fees will be paid
997    /// to perform the refund.
998    pub async fn prepare_refund(
999        &self,
1000        req: PrepareRefundRequest,
1001    ) -> SdkResult<PrepareRefundResponse> {
1002        Ok(self.btc_receive_swapper.prepare_refund(req).await?)
1003    }
1004
1005    /// Construct and broadcast a refund transaction for a failed/expired swap
1006    ///
1007    /// Returns the txid of the refund transaction.
1008    pub async fn refund(&self, req: RefundRequest) -> SdkResult<RefundResponse> {
1009        Ok(self.btc_receive_swapper.refund(req).await?)
1010    }
1011
1012    pub async fn onchain_payment_limits(&self) -> SdkResult<OnchainPaymentLimitsResponse> {
1013        let fee_info = self.btc_send_swapper.fetch_reverse_swap_fees().await?;
1014        debug!("Reverse swap pair info: {fee_info:?}");
1015        let max_amt_current_channels = self.max_reverse_swap_amount().await?;
1016        debug!("Max send amount possible with current channels: {max_amt_current_channels:?}");
1017
1018        Ok(OnchainPaymentLimitsResponse {
1019            min_sat: fee_info.min,
1020            max_sat: fee_info.max,
1021            max_payable_sat: max_amt_current_channels,
1022        })
1023    }
1024
1025    /// Supersedes [BreezServices::fetch_reverse_swap_fees]
1026    ///
1027    /// ### Errors
1028    ///
1029    /// - `OutOfRange`: This indicates the send amount is outside the range of minimum and maximum
1030    ///   values returned by [BreezServices::onchain_payment_limits]. When you get this error, please first call
1031    ///   [BreezServices::onchain_payment_limits] to get the new limits, before calling this method again.
1032    pub async fn prepare_onchain_payment(
1033        &self,
1034        req: PrepareOnchainPaymentRequest,
1035    ) -> Result<PrepareOnchainPaymentResponse, SendOnchainError> {
1036        let fees_claim = BTCSendSwap::calculate_claim_tx_fee(req.claim_tx_feerate)?;
1037        BTCSendSwap::validate_claim_tx_fee(fees_claim)?;
1038
1039        let fee_info = self.btc_send_swapper.fetch_reverse_swap_fees().await?;
1040
1041        // Calculate (send_amt, recv_amt) from the inputs and fees
1042        let fees_lockup = fee_info.fees_lockup;
1043        let p = fee_info.fees_percentage;
1044        let (send_amt, recv_amt) = match req.amount_type {
1045            SwapAmountType::Send => {
1046                let temp_send_amt = req.amount_sat;
1047                let service_fees = swap_out::get_service_fee_sat(temp_send_amt, p);
1048                let total_fees = service_fees + fees_lockup + fees_claim;
1049                ensure_sdk!(
1050                    temp_send_amt > total_fees,
1051                    SendOnchainError::generic(
1052                        "Send amount is not high enough to account for all fees"
1053                    )
1054                );
1055
1056                (temp_send_amt, temp_send_amt - total_fees)
1057            }
1058            SwapAmountType::Receive => {
1059                let temp_recv_amt = req.amount_sat;
1060                let send_amt_minus_service_fee = temp_recv_amt + fees_lockup + fees_claim;
1061                let temp_send_amt = swap_out::get_invoice_amount_sat(send_amt_minus_service_fee, p);
1062
1063                (temp_send_amt, temp_recv_amt)
1064            }
1065        };
1066
1067        let is_send_in_range = send_amt >= fee_info.min && send_amt <= fee_info.max;
1068        ensure_sdk!(is_send_in_range, SendOnchainError::OutOfRange);
1069
1070        Ok(PrepareOnchainPaymentResponse {
1071            fees_hash: fee_info.fees_hash.clone(),
1072            fees_percentage: p,
1073            fees_lockup,
1074            fees_claim,
1075            sender_amount_sat: send_amt,
1076            recipient_amount_sat: recv_amt,
1077            total_fees: send_amt - recv_amt,
1078        })
1079    }
1080
1081    /// Creates a reverse swap and attempts to pay the HODL invoice
1082    pub async fn pay_onchain(
1083        &self,
1084        req: PayOnchainRequest,
1085    ) -> Result<PayOnchainResponse, SendOnchainError> {
1086        ensure_sdk!(
1087            req.prepare_res.sender_amount_sat > req.prepare_res.recipient_amount_sat,
1088            SendOnchainError::generic("Send amount must be bigger than receive amount")
1089        );
1090
1091        ensure_sdk!(self.in_progress_onchain_payments().await?.is_empty(), SendOnchainError::Generic { err:
1092            "You can only start a new one after after the ongoing ones finish. \
1093            Use the in_progress_onchain_payments method to get an overview of currently ongoing reverse swaps".into(),
1094        });
1095
1096        let full_rsi = self.btc_send_swapper.create_reverse_swap(req).await?;
1097        let reverse_swap_info = self
1098            .btc_send_swapper
1099            .convert_reverse_swap_info(full_rsi.clone())
1100            .await?;
1101        self.do_sync(false).await?;
1102
1103        if let Some(webhook_url) = self.persister.get_webhook_url()? {
1104            let address = &full_rsi
1105                .get_lockup_address(self.config.network)?
1106                .to_string();
1107            info!("Registering for onchain tx notification for address {address}");
1108            self.register_onchain_tx_notification(address, &webhook_url)
1109                .await?;
1110        }
1111        Ok(PayOnchainResponse { reverse_swap_info })
1112    }
1113
1114    /// Returns the blocking [ReverseSwapInfo]s that are in progress.
1115    pub async fn in_progress_onchain_payments(&self) -> SdkResult<Vec<ReverseSwapInfo>> {
1116        let full_rsis = self.btc_send_swapper.list_blocking().await?;
1117
1118        let mut rsis = vec![];
1119        for full_rsi in full_rsis {
1120            let rsi = self
1121                .btc_send_swapper
1122                .convert_reverse_swap_info(full_rsi)
1123                .await?;
1124            rsis.push(rsi);
1125        }
1126
1127        Ok(rsis)
1128    }
1129
1130    /// Execute a command directly on the NodeAPI interface.
1131    /// Mainly used for debugging.
1132    pub async fn execute_dev_command(&self, command: String) -> SdkResult<String> {
1133        let dev_cmd_res = DevCommand::from_str(&command);
1134
1135        match dev_cmd_res {
1136            Ok(dev_cmd) => match dev_cmd {
1137                DevCommand::GenerateDiagnosticData => self.generate_diagnostic_data().await,
1138            },
1139            Err(_) => Ok(crate::serializer::to_string_pretty(
1140                &self.node_api.execute_command(command).await?,
1141            )?),
1142        }
1143    }
1144
1145    // Collects various user data from the node and the sdk storage.
1146    // This is used for debugging and support purposes only.
1147    pub async fn generate_diagnostic_data(&self) -> SdkResult<String> {
1148        let now_sec = SystemTime::now()
1149            .duration_since(UNIX_EPOCH)
1150            .map(|d| d.as_secs())
1151            .unwrap_or_default();
1152        let node_data = self
1153            .node_api
1154            .generate_diagnostic_data()
1155            .await
1156            .unwrap_or_else(|e| json!({"error": e.to_string()}));
1157        let sdk_data = self
1158            .generate_sdk_diagnostic_data()
1159            .await
1160            .unwrap_or_else(|e| json!({"error": e.to_string()}));
1161        let result = json!({
1162            "timestamp": now_sec,
1163            "node": node_data,
1164            "sdk": sdk_data
1165        });
1166        Ok(crate::serializer::to_string_pretty(&result)?)
1167    }
1168
1169    /// This method syncs the local state with the remote node state.
1170    /// The synced items are as follows:
1171    /// * node state - General information about the node and its liquidity status
1172    /// * channels - The list of channels and their status
1173    /// * payments - The incoming/outgoing payments
1174    pub async fn sync(&self) -> SdkResult<()> {
1175        Ok(self.do_sync(false).await?)
1176    }
1177
1178    async fn do_sync(&self, match_local_balance: bool) -> Result<()> {
1179        let start = Instant::now();
1180        let node_pubkey = self.node_api.node_id().await?;
1181        self.connect_lsp_peer(node_pubkey).await?;
1182
1183        // First query the changes since last sync state.
1184        let sync_state = self.persister.get_sync_state()?;
1185        let new_data = &self
1186            .node_api
1187            .pull_changed(sync_state.clone(), match_local_balance)
1188            .await?;
1189
1190        debug!(
1191            "pull changed old state={:?} new state={:?}",
1192            sync_state, new_data.sync_state
1193        );
1194
1195        // update node state and channels state
1196        self.persister.set_node_state(&new_data.node_state)?;
1197
1198        let channels_before_update = self.persister.list_channels()?;
1199        self.persister.update_channels(&new_data.channels)?;
1200        let channels_after_update = self.persister.list_channels()?;
1201
1202        // Fetch the static backup if needed and persist it
1203        if channels_before_update.len() != channels_after_update.len() {
1204            info!("fetching static backup file from node");
1205            let backup = self.node_api.static_backup().await?;
1206            self.persister.set_static_backup(backup)?;
1207        }
1208
1209        //fetch closed_channel and convert them to Payment items.
1210        let mut closed_channel_payments: Vec<Payment> = vec![];
1211        for closed_channel in
1212            self.persister.list_channels()?.into_iter().filter(|c| {
1213                c.state == ChannelState::Closed || c.state == ChannelState::PendingClose
1214            })
1215        {
1216            let closed_channel_tx = self.closed_channel_to_transaction(closed_channel).await?;
1217            closed_channel_payments.push(closed_channel_tx);
1218        }
1219
1220        // update both closed channels and lightning transaction payments
1221        let mut payments = closed_channel_payments;
1222        payments.extend(new_data.payments.clone());
1223        self.persister.delete_pseudo_payments()?;
1224        self.persister.insert_or_update_payments(&payments, false)?;
1225        let duration = start.elapsed();
1226        info!("Sync duration: {duration:?}");
1227
1228        // update the cached sync state
1229        self.persister.set_sync_state(&new_data.sync_state)?;
1230        self.notify_event_listeners(BreezEvent::Synced).await?;
1231        Ok(())
1232    }
1233
1234    /// Connects to the selected LSP peer.
1235    /// This validates if the selected LSP is still in [`list_lsps`].
1236    /// If not or no LSP is selected, it selects the first LSP in [`list_lsps`].
1237    async fn connect_lsp_peer(&self, node_pubkey: String) -> SdkResult<()> {
1238        let lsps = self.lsp_api.list_lsps(node_pubkey).await?;
1239        let lsp = match self
1240            .persister
1241            .get_lsp_id()?
1242            .and_then(|lsp_id| lsps.iter().find(|lsp| lsp.id == lsp_id))
1243            .or_else(|| lsps.first())
1244        {
1245            Some(lsp) => lsp.clone(),
1246            None => return Ok(()),
1247        };
1248
1249        self.persister.set_lsp(lsp.id, Some(lsp.pubkey.clone()))?;
1250        let node_state = match self.node_info() {
1251            Ok(node_state) => node_state,
1252            Err(_) => return Ok(()),
1253        };
1254
1255        let node_id = lsp.pubkey;
1256        let address = lsp.host;
1257        let lsp_connected = node_state
1258            .connected_peers
1259            .iter()
1260            .any(|e| e == node_id.as_str());
1261        if !lsp_connected {
1262            debug!("connecting to lsp {}@{}", node_id.clone(), address.clone());
1263            self.node_api
1264                .connect_peer(node_id.clone(), address.clone())
1265                .await
1266                .map_err(|e| SdkError::ServiceConnectivity {
1267                    err: format!("(LSP: {node_id}) Failed to connect: {e}"),
1268                })?;
1269            debug!("connected to lsp {node_id}@{address}");
1270        }
1271
1272        Ok(())
1273    }
1274
1275    fn persist_pending_payment(
1276        &self,
1277        invoice: &LNInvoice,
1278        amount_msat: u64,
1279        label: Option<String>,
1280    ) -> Result<(), SendPaymentError> {
1281        self.persister.insert_or_update_payments(
1282            &[Payment {
1283                id: invoice.payment_hash.clone(),
1284                payment_type: PaymentType::Sent,
1285                payment_time: SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64,
1286                amount_msat,
1287                fee_msat: 0,
1288                status: PaymentStatus::Pending,
1289                error: None,
1290                description: invoice.description.clone(),
1291                details: PaymentDetails::Ln {
1292                    data: LnPaymentDetails {
1293                        payment_hash: invoice.payment_hash.clone(),
1294                        label: label.unwrap_or_default(),
1295                        destination_pubkey: invoice.payee_pubkey.clone(),
1296                        payment_preimage: String::new(),
1297                        keysend: false,
1298                        bolt11: invoice.bolt11.clone(),
1299                        lnurl_success_action: None,
1300                        lnurl_pay_domain: None,
1301                        lnurl_pay_comment: None,
1302                        ln_address: None,
1303                        lnurl_metadata: None,
1304                        lnurl_withdraw_endpoint: None,
1305                        swap_info: None,
1306                        reverse_swap_info: None,
1307                        pending_expiration_block: None,
1308                        open_channel_bolt11: None,
1309                    },
1310                },
1311                metadata: None,
1312            }],
1313            true,
1314        )?;
1315
1316        self.persister.insert_payment_external_info(
1317            &invoice.payment_hash,
1318            PaymentExternalInfo {
1319                lnurl_pay_success_action: None,
1320                lnurl_pay_domain: None,
1321                lnurl_pay_comment: None,
1322                lnurl_metadata: None,
1323                ln_address: None,
1324                lnurl_withdraw_endpoint: None,
1325                attempted_amount_msat: invoice.amount_msat.map_or(Some(amount_msat), |_| None),
1326                attempted_error: None,
1327            },
1328        )?;
1329        Ok(())
1330    }
1331
1332    async fn on_payment_completed(
1333        &self,
1334        node_id: String,
1335        invoice: Option<LNInvoice>,
1336        label: Option<String>,
1337        payment_res: Result<Payment, SendPaymentError>,
1338    ) -> Result<Payment, SendPaymentError> {
1339        self.do_sync(false).await?;
1340        match payment_res {
1341            Ok(payment) => {
1342                self.notify_event_listeners(BreezEvent::PaymentSucceed {
1343                    details: payment.clone(),
1344                })
1345                .await?;
1346                Ok(payment)
1347            }
1348            Err(e) => {
1349                if let Some(invoice) = invoice.clone() {
1350                    self.persister.update_payment_attempted_error(
1351                        &invoice.payment_hash,
1352                        Some(e.to_string()),
1353                    )?;
1354                }
1355                self.notify_event_listeners(BreezEvent::PaymentFailed {
1356                    details: PaymentFailedData {
1357                        error: e.to_string(),
1358                        node_id,
1359                        invoice,
1360                        label,
1361                    },
1362                })
1363                .await?;
1364                Err(e)
1365            }
1366        }
1367    }
1368
1369    async fn on_event(&self, e: BreezEvent) -> Result<()> {
1370        debug!("breez services got event {e:?}");
1371        self.notify_event_listeners(e.clone()).await
1372    }
1373
1374    async fn notify_event_listeners(&self, e: BreezEvent) -> Result<()> {
1375        if let Err(err) = self.btc_receive_swapper.on_event(e.clone()).await {
1376            debug!("btc_receive_swapper failed to process event {e:?}: {err:?}")
1377        };
1378        if let Err(err) = self.btc_send_swapper.on_event(e.clone()).await {
1379            debug!("btc_send_swapper failed to process event {e:?}: {err:?}")
1380        };
1381
1382        if self.event_listener.is_some() {
1383            self.event_listener.as_ref().unwrap().on_event(e.clone())
1384        }
1385        Ok(())
1386    }
1387
1388    /// Convenience method to look up LSP info based on current LSP ID
1389    pub async fn lsp_info(&self) -> SdkResult<LspInformation> {
1390        get_lsp(self.persister.clone(), self.lsp_api.clone()).await
1391    }
1392
1393    async fn get_lsps(&self) -> SdkResult<Vec<LspInformation>> {
1394        get_lsps(self.persister.clone(), self.lsp_api.clone()).await
1395    }
1396
1397    /// Get the recommended fees for onchain transactions
1398    pub async fn recommended_fees(&self) -> SdkResult<RecommendedFees> {
1399        self.chain_service.recommended_fees().await
1400    }
1401
1402    /// Get the full default config for a specific environment type
1403    pub fn default_config(
1404        env_type: EnvironmentType,
1405        api_key: String,
1406        node_config: NodeConfig,
1407    ) -> Config {
1408        match env_type {
1409            EnvironmentType::Production => Config::production(api_key, node_config),
1410            EnvironmentType::Staging => Config::staging(api_key, node_config),
1411            EnvironmentType::Regtest => Config::regtest(api_key, node_config),
1412        }
1413    }
1414
1415    /// Get the static backup data from the persistent storage.
1416    /// This data enables the user to recover the node in an external core lightning node.
1417    /// See here for instructions on how to recover using this data: <https://docs.corelightning.org/docs/backup-and-recovery#backing-up-using-static-channel-backup>
1418    pub fn static_backup(req: StaticBackupRequest) -> SdkResult<StaticBackupResponse> {
1419        let storage = SqliteStorage::new(req.working_dir);
1420        Ok(StaticBackupResponse {
1421            backup: storage.get_static_backup()?,
1422        })
1423    }
1424
1425    /// Fetches the service health check from the support API.
1426    pub async fn service_health_check(api_key: String) -> SdkResult<ServiceHealthCheckResponse> {
1427        let support_api: Arc<dyn SupportAPI> = Arc::new(BreezServer::new(
1428            PRODUCTION_BREEZSERVER_URL.to_string(),
1429            Some(api_key),
1430        )?);
1431
1432        support_api.service_health_check().await
1433    }
1434
1435    /// Generates an url that can be used by a third part provider to buy Bitcoin with fiat currency.
1436    ///
1437    /// A user-selected [OpeningFeeParams] can be optionally set in the argument. If set, and the
1438    /// operation requires a new channel, the SDK will try to use the given fee params.
1439    pub async fn buy_bitcoin(
1440        &self,
1441        req: BuyBitcoinRequest,
1442    ) -> Result<BuyBitcoinResponse, ReceiveOnchainError> {
1443        let swap_info = self
1444            .receive_onchain(ReceiveOnchainRequest {
1445                opening_fee_params: req.opening_fee_params,
1446            })
1447            .await?;
1448        let url = self
1449            .buy_bitcoin_api
1450            .buy_bitcoin(req.provider, &swap_info, req.redirect_url)
1451            .await?;
1452
1453        Ok(BuyBitcoinResponse {
1454            url,
1455            opening_fee_params: swap_info.channel_opening_fees,
1456        })
1457    }
1458
1459    /// Starts the BreezServices background threads.
1460    ///
1461    /// Internal method. Should only be used as part of [BreezServices::start]
1462    async fn start_background_tasks(self: &Arc<BreezServices>) -> SdkResult<()> {
1463        // start the signer
1464        let (shutdown_signer_sender, signer_signer_receiver) = watch::channel(());
1465        self.start_signer(signer_signer_receiver).await;
1466        self.start_node_keep_alive(self.shutdown_sender.subscribe())
1467            .await;
1468
1469        // Sync node state
1470        match self.persister.get_node_state()? {
1471            Some(node) => {
1472                info!("Starting existing node {}", node.id);
1473                self.connect_lsp_peer(node.id).await?;
1474            }
1475            None => {
1476                // In case it is a first run we sync in foreground to get the node state.
1477                info!("First run, syncing in foreground");
1478                self.sync().await?;
1479                info!("First run, finished running syncing in foreground");
1480            }
1481        }
1482
1483        // start backup watcher
1484        self.start_backup_watcher().await?;
1485
1486        //track backup events
1487        self.track_backup_events().await;
1488
1489        //track swap events
1490        self.track_swap_events().await;
1491
1492        // track paid invoices
1493        self.track_invoices().await;
1494
1495        // track new blocks
1496        self.track_new_blocks().await;
1497
1498        // track logs
1499        self.track_logs().await;
1500
1501        // Stop signer on shutdown
1502        let mut shutdown_receiver = self.shutdown_sender.subscribe();
1503        tokio::spawn(async move {
1504            _ = shutdown_receiver.changed().await;
1505            _ = shutdown_signer_sender.send(());
1506            debug!("Received the signal to exit signer");
1507        });
1508
1509        self.init_chainservice_urls().await?;
1510
1511        Ok(())
1512    }
1513
1514    async fn start_signer(self: &Arc<BreezServices>, mut shutdown_receiver: watch::Receiver<()>) {
1515        let node_api = self.node_api.clone();
1516
1517        tokio::spawn(async move {
1518            loop {
1519                let (tx, rx) = mpsc::channel(1);
1520                let is_shutdown = tokio::select! {
1521                    _ = node_api.start(rx) => {
1522                        tokio::time::sleep(Duration::from_secs(1)).await;
1523                        false
1524                    }
1525
1526                    _ = shutdown_receiver.changed() => {
1527                        true
1528                    }
1529                };
1530
1531                debug!("shutting down signer");
1532                drop(tx); // Dropping the sender explicitly to notify the receiver.
1533
1534                if is_shutdown {
1535                    return;
1536                }
1537            }
1538        });
1539    }
1540
1541    async fn start_node_keep_alive(
1542        self: &Arc<BreezServices>,
1543        shutdown_receiver: watch::Receiver<()>,
1544    ) {
1545        let cloned = self.clone();
1546        tokio::spawn(async move {
1547            cloned.node_api.start_keep_alive(shutdown_receiver).await;
1548        });
1549    }
1550
1551    async fn start_backup_watcher(self: &Arc<BreezServices>) -> Result<()> {
1552        self.backup_watcher
1553            .start(self.shutdown_sender.subscribe())
1554            .await
1555            .map_err(|e| anyhow!("Failed to start backup watcher: {e}"))?;
1556
1557        // Restore backup state and request backup on start if needed
1558        let force_backup = self
1559            .persister
1560            .get_last_sync_version()
1561            .map_err(|e| anyhow!("Failed to read last sync version: {e}"))?
1562            .is_none();
1563        self.backup_watcher
1564            .request_backup(BackupRequest::new(force_backup))
1565            .await
1566            .map_err(|e| anyhow!("Failed to request backup: {e}"))
1567    }
1568
1569    async fn track_backup_events(self: &Arc<BreezServices>) {
1570        let cloned = self.clone();
1571        tokio::spawn(async move {
1572            let mut events_stream = cloned.backup_watcher.subscribe_events();
1573            let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1574            loop {
1575                tokio::select! {
1576                    backup_event = events_stream.recv() => {
1577                        if let Ok(e) = backup_event {
1578                            if let Err(err) = cloned.notify_event_listeners(e).await {
1579                                error!("error handling backup event: {err:?}");
1580                            }
1581                        }
1582                        let backup_status = cloned.backup_status();
1583                        info!("backup status: {backup_status:?}");
1584                    },
1585                    _ = shutdown_receiver.changed() => {
1586                        debug!("Backup watcher task completed");
1587                        break;
1588                    }
1589                }
1590            }
1591        });
1592    }
1593
1594    async fn track_swap_events(self: &Arc<BreezServices>) {
1595        let cloned = self.clone();
1596        tokio::spawn(async move {
1597            let mut swap_events_stream = cloned.btc_receive_swapper.subscribe_status_changes();
1598            let mut rev_swap_events_stream = cloned.btc_send_swapper.subscribe_status_changes();
1599            let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1600            loop {
1601                tokio::select! {
1602                    swap_event = swap_events_stream.recv() => {
1603                        if let Ok(e) = swap_event {
1604                            if let Err(err) = cloned.notify_event_listeners(e).await {
1605                                error!("error handling swap event: {err:?}");
1606                            }
1607                        }
1608                    },
1609                    rev_swap_event = rev_swap_events_stream.recv() => {
1610                        if let Ok(e) = rev_swap_event {
1611                            if let Err(err) = cloned.notify_event_listeners(e).await {
1612                                error!("error handling reverse swap event: {err:?}");
1613                            }
1614                        }
1615                    },
1616                    _ = shutdown_receiver.changed() => {
1617                        debug!("Swap events handling task completed");
1618                        break;
1619                    }
1620                }
1621            }
1622        });
1623    }
1624
1625    async fn track_invoices(self: &Arc<BreezServices>) {
1626        let cloned = self.clone();
1627        tokio::spawn(async move {
1628            let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1629            loop {
1630                if shutdown_receiver.has_changed().unwrap_or(true) {
1631                    return;
1632                }
1633                let mut invoice_stream = match cloned.node_api.stream_incoming_payments().await {
1634                    Ok(invoice_stream) => invoice_stream,
1635                    Err(e) => {
1636                        warn!("stream incoming payments returned error: {e:?}");
1637                        tokio::select! {
1638                            _ = sleep(Duration::from_secs(1)) => {
1639                                continue
1640                            }
1641                            _ = shutdown_receiver.changed() => {
1642                                debug!("Invoice tracking task has completed");
1643                                return;
1644                            }
1645                        };
1646                    }
1647                };
1648
1649                loop {
1650                    let paid_invoice_res = tokio::select! {
1651                        paid_invoice_res = invoice_stream.next() => paid_invoice_res,
1652                        _ = shutdown_receiver.changed() => {
1653                            debug!("Invoice tracking task has completed");
1654                            return;
1655                        }
1656                    };
1657
1658                    let p = match paid_invoice_res {
1659                        Some(p) => p,
1660                        None => {
1661                            debug!("invoice stream got None");
1662                            break;
1663                        }
1664                    };
1665
1666                    debug!("invoice stream got new invoice");
1667                    let mut payment: Option<crate::models::Payment> = p.clone().try_into().ok();
1668                    if let Some(ref p) = payment {
1669                        let res = cloned
1670                            .persister
1671                            .insert_or_update_payments(&vec![p.clone()], false);
1672                        debug!("paid invoice was added to payments list {res:?}");
1673                        if let Ok(Some(mut node_info)) = cloned.persister.get_node_state() {
1674                            node_info.channels_balance_msat += p.amount_msat;
1675                            let res = cloned.persister.set_node_state(&node_info);
1676                            debug!("channel balance was updated {res:?}");
1677                        }
1678                        payment = cloned
1679                            .persister
1680                            .get_payment_by_hash(&p.id)
1681                            .unwrap_or(payment);
1682                    }
1683                    _ = cloned
1684                        .on_event(BreezEvent::InvoicePaid {
1685                            details: InvoicePaidDetails {
1686                                payment_hash: hex::encode(p.payment_hash),
1687                                bolt11: p.bolt11,
1688                                payment,
1689                            },
1690                        })
1691                        .await;
1692                    if let Err(e) = cloned.do_sync(true).await {
1693                        error!("failed to sync after paid invoice: {e:?}");
1694                    }
1695                }
1696
1697                tokio::select! {
1698                    _ = sleep(Duration::from_secs(1)) => {
1699                        continue
1700                    }
1701                    _ = shutdown_receiver.changed() => {
1702                        debug!("Invoice tracking task has completed");
1703                        return;
1704                    }
1705                };
1706            }
1707        });
1708    }
1709
1710    async fn track_logs(self: &Arc<BreezServices>) {
1711        let cloned = self.clone();
1712        tokio::spawn(async move {
1713            let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1714            loop {
1715                if shutdown_receiver.has_changed().unwrap_or(true) {
1716                    return;
1717                }
1718                let mut log_stream = match cloned.node_api.stream_log_messages().await {
1719                    Ok(log_stream) => log_stream,
1720                    Err(e) => {
1721                        warn!("stream log messages returned error: {e:?}");
1722                        tokio::select! {
1723                            _ = sleep(Duration::from_secs(1)) => {
1724                                continue
1725                            }
1726                            _ = shutdown_receiver.changed() => {
1727                                debug!("Invoice tracking task has completed");
1728                                return;
1729                            }
1730                        };
1731                    }
1732                };
1733
1734                loop {
1735                    let log_message_res = tokio::select! {
1736                        log_message_res = log_stream.next() => log_message_res,
1737                        _ = shutdown_receiver.changed() => {
1738                            debug!("Track logs task has completed");
1739                            return;
1740                        }
1741                    };
1742
1743                    match log_message_res {
1744                        Some(l) => info!("node-logs: {l}"),
1745                        None => {
1746                            // stream is closed, renew it
1747                            break;
1748                        }
1749                    };
1750                }
1751
1752                tokio::select! {
1753                    _ = sleep(Duration::from_secs(1)) => {
1754                        continue
1755                    }
1756                    _ = shutdown_receiver.changed() => {
1757                        debug!("Invoice tracking task has completed");
1758                        return;
1759                    }
1760                };
1761            }
1762        });
1763    }
1764
1765    async fn track_new_blocks(self: &Arc<BreezServices>) {
1766        let cloned = self.clone();
1767        tokio::spawn(async move {
1768            let mut current_block: u32 = 0;
1769            let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1770            let mut interval = tokio::time::interval(Duration::from_secs(30));
1771            interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
1772            loop {
1773                tokio::select! {
1774                    _ = interval.tick() => { }
1775
1776                    _ = shutdown_receiver.changed() => {
1777                        debug!("New blocks task has completed");
1778                        return;
1779                    }
1780                }
1781
1782                let next_block = match cloned.chain_service.current_tip().await {
1783                    Ok(next_block) => next_block,
1784                    Err(e) => {
1785                        error!("failed to fetch next block {e}");
1786                        continue;
1787                    }
1788                };
1789
1790                debug!("got tip {next_block:?}");
1791                if next_block > current_block {
1792                    _ = cloned.sync().await;
1793                    _ = cloned
1794                        .on_event(BreezEvent::NewBlock { block: next_block })
1795                        .await;
1796                }
1797                current_block = next_block
1798            }
1799        });
1800    }
1801
1802    async fn init_chainservice_urls(&self) -> Result<()> {
1803        let breez_server = Arc::new(BreezServer::new(
1804            PRODUCTION_BREEZSERVER_URL.to_string(),
1805            None,
1806        )?);
1807        let persister = &self.persister;
1808
1809        let cloned_breez_server = breez_server.clone();
1810        let cloned_persister = persister.clone();
1811        tokio::spawn(async move {
1812            match cloned_breez_server.fetch_mempoolspace_urls().await {
1813                Ok(fresh_urls) => {
1814                    if let Err(e) = cloned_persister.set_mempoolspace_base_urls(fresh_urls) {
1815                        error!("Failed to cache mempool.space URLs: {e}");
1816                    }
1817                }
1818                Err(e) => error!("Failed to fetch mempool.space URLs: {e}"),
1819            }
1820        });
1821
1822        Ok(())
1823    }
1824
1825    /// Configures a global SDK logger that will log to file and will forward log events to
1826    /// an optional application-specific logger.
1827    ///
1828    /// If called, it should be called before any SDK methods (for example, before `connect`).
1829    ///
1830    /// It must be called only once in the application lifecycle. Alternatively, if the application
1831    /// already uses a globally-registered logger, this method shouldn't be called at all.
1832    ///
1833    /// ### Arguments
1834    ///
1835    /// - `log_dir`: Location where the the SDK log file will be created. The directory must already exist.
1836    ///
1837    /// - `app_logger`: Optional application logger.
1838    ///
1839    /// If the application is to use its own logger, but would also like the SDK to log SDK-specific
1840    /// log output to a file in the configured `log_dir`, then do not register the
1841    /// app-specific logger as a global logger and instead call this method with the app logger as an arg.
1842    ///
1843    /// ### Logging Configuration
1844    ///
1845    /// Setting `breez_sdk_core::input_parser=debug` will include in the logs the raw payloads received
1846    /// when interacting with JSON endpoints, for example those used during all LNURL workflows.
1847    ///
1848    /// ### Errors
1849    ///
1850    /// An error is thrown if the log file cannot be created in the working directory.
1851    ///
1852    /// An error is thrown if a global logger is already configured.
1853    pub fn init_logging(log_dir: &str, app_logger: Option<Box<dyn log::Log>>) -> Result<()> {
1854        let target_log_file = Box::new(
1855            OpenOptions::new()
1856                .create(true)
1857                .append(true)
1858                .open(format!("{log_dir}/sdk.log"))
1859                .map_err(|e| anyhow!("Can't create log file: {e}"))?,
1860        );
1861        let logger = env_logger::Builder::new()
1862            .target(env_logger::Target::Pipe(target_log_file))
1863            .parse_filters(
1864                r#"
1865                info,
1866                breez_sdk_core=debug,
1867                sdk_common=debug,
1868                gl_client=debug,
1869                h2=warn,
1870                hyper=warn,
1871                lightning_signer=warn,
1872                reqwest=warn,
1873                rustls=warn,
1874                rustyline=warn,
1875                vls_protocol_signer=warn
1876            "#,
1877            )
1878            .format(|buf, record| {
1879                writeln!(
1880                    buf,
1881                    "[{} {} {}:{}] {}",
1882                    Local::now().format("%Y-%m-%d %H:%M:%S%.3f"),
1883                    record.level(),
1884                    record.module_path().unwrap_or("unknown"),
1885                    record.line().unwrap_or(0),
1886                    record.args()
1887                )
1888            })
1889            .build();
1890
1891        let global_logger = GlobalSdkLogger {
1892            logger,
1893            log_listener: app_logger,
1894        };
1895
1896        log::set_boxed_logger(Box::new(global_logger))
1897            .map_err(|e| anyhow!("Failed to set global logger: {e}"))?;
1898        log::set_max_level(LevelFilter::Trace);
1899
1900        Ok(())
1901    }
1902
1903    async fn lookup_chain_service_closing_outspend(
1904        &self,
1905        channel: crate::models::Channel,
1906    ) -> Result<Option<Outspend>> {
1907        match channel.funding_outnum {
1908            None => Ok(None),
1909            Some(outnum) => {
1910                // Find the output tx that was used to fund the channel
1911                let outspends = self
1912                    .chain_service
1913                    .transaction_outspends(channel.funding_txid.clone())
1914                    .await?;
1915
1916                Ok(outspends.get(outnum as usize).cloned())
1917            }
1918        }
1919    }
1920
1921    /// Chain service lookup of relevant channel closing fields (closed_at, closing_txid).
1922    ///
1923    /// Should be used sparingly because it involves a network lookup.
1924    async fn lookup_channel_closing_data(
1925        &self,
1926        channel: &crate::models::Channel,
1927    ) -> Result<(Option<u64>, Option<String>)> {
1928        let maybe_outspend_res = self
1929            .lookup_chain_service_closing_outspend(channel.clone())
1930            .await;
1931        let maybe_outspend: Option<Outspend> = match maybe_outspend_res {
1932            Ok(s) => s,
1933            Err(e) => {
1934                error!("Failed to lookup channel closing data: {e:?}");
1935                None
1936            }
1937        };
1938
1939        let maybe_closed_at = maybe_outspend
1940            .clone()
1941            .and_then(|outspend| outspend.status)
1942            .and_then(|s| s.block_time);
1943        let maybe_closing_txid = maybe_outspend.and_then(|outspend| outspend.txid);
1944
1945        Ok((maybe_closed_at, maybe_closing_txid))
1946    }
1947
1948    async fn closed_channel_to_transaction(
1949        &self,
1950        channel: crate::models::Channel,
1951    ) -> Result<Payment> {
1952        let (payment_time, closing_txid) = match (channel.closed_at, channel.closing_txid.clone()) {
1953            (Some(closed_at), Some(closing_txid)) => (closed_at as i64, Some(closing_txid)),
1954            (_, _) => {
1955                // If any of the two closing-related fields are empty, we look them up and persist them
1956                let (maybe_closed_at, maybe_closing_txid) =
1957                    self.lookup_channel_closing_data(&channel).await?;
1958
1959                let processed_closed_at = match maybe_closed_at {
1960                    None => {
1961                        warn!("Blocktime could not be determined for from closing outspend, defaulting closed_at to epoch time");
1962                        SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()
1963                    }
1964                    Some(block_time) => block_time,
1965                };
1966
1967                let mut updated_channel = channel.clone();
1968                updated_channel.closed_at = Some(processed_closed_at);
1969                // If no closing txid found, we persist it as None, so it will be looked-up next time
1970                updated_channel.closing_txid.clone_from(&maybe_closing_txid);
1971                self.persister.insert_or_update_channel(updated_channel)?;
1972
1973                (processed_closed_at as i64, maybe_closing_txid)
1974            }
1975        };
1976
1977        Ok(Payment {
1978            id: channel.funding_txid.clone(),
1979            payment_type: PaymentType::ClosedChannel,
1980            payment_time,
1981            amount_msat: channel.local_balance_msat,
1982            fee_msat: 0,
1983            status: match channel.state {
1984                ChannelState::PendingClose => PaymentStatus::Pending,
1985                _ => PaymentStatus::Complete,
1986            },
1987            description: Some("Closed Channel".to_string()),
1988            details: PaymentDetails::ClosedChannel {
1989                data: ClosedChannelPaymentDetails {
1990                    short_channel_id: channel.short_channel_id,
1991                    state: channel.state,
1992                    funding_txid: channel.funding_txid,
1993                    closing_txid,
1994                },
1995            },
1996            error: None,
1997            metadata: None,
1998        })
1999    }
2000
2001    /// Register for webhook callbacks at the given `webhook_url`.
2002    ///
2003    /// More specifically, it registers for the following types of callbacks:
2004    /// - a payment is received
2005    /// - a swap tx is confirmed
2006    ///
2007    /// This method should be called every time the application is started and when the `webhook_url` changes.
2008    /// For example, if the `webhook_url` contains a push notification token and the token changes after
2009    /// the application was started, then this method should be called to register for callbacks at
2010    /// the new correct `webhook_url`. To unregister a webhook call [BreezServices::unregister_webhook].
2011    pub async fn register_webhook(&self, webhook_url: String) -> SdkResult<()> {
2012        info!("Registering for webhook notifications");
2013        let is_new_webhook_url = match self.persister.get_webhook_url()? {
2014            None => true,
2015            Some(cached_webhook_url) => cached_webhook_url != webhook_url,
2016        };
2017        match is_new_webhook_url {
2018            false => debug!("Webhook URL not changed, no need to (re-)register for monitored swap tx notifications"),
2019            true => {
2020                for swap in self.btc_receive_swapper.list_swaps(ListSwapsRequest {
2021                    status: Some(SwapStatus::unexpired()),
2022                    ..Default::default()
2023                })?
2024                {
2025                    let swap_address = &swap.bitcoin_address;
2026                    info!("Found non-refundable monitored swap with address {swap_address}, registering for onchain tx notifications");
2027                    self.register_onchain_tx_notification(swap_address, &webhook_url)
2028                        .await?;
2029                }
2030
2031                for rev_swap in self
2032                    .btc_send_swapper
2033                    .list_monitored()
2034                    .await?
2035                    .iter()
2036                {
2037                    let lockup_address = &rev_swap.get_lockup_address(self.config.network)?.to_string();
2038                    info!("Found monitored reverse swap with address {lockup_address}, registering for onchain tx notifications");
2039                    self.register_onchain_tx_notification(lockup_address, &webhook_url)
2040                        .await?;
2041                }
2042            }
2043        }
2044
2045        // Register for LN payment notifications on every call, since these webhook registrations
2046        // timeout after 14 days of not being used
2047        self.register_payment_notifications(webhook_url.clone())
2048            .await?;
2049
2050        // Only cache the webhook URL if callbacks were successfully registered for it.
2051        // If any step above failed, not caching it allows the caller to re-trigger the registrations
2052        // by calling the method again
2053        self.persister.set_webhook_url(webhook_url)?;
2054        Ok(())
2055    }
2056
2057    /// Unregister webhook callbacks for the given `webhook_url`.
2058    ///
2059    /// When called, it unregisters for the following types of callbacks:
2060    /// - a payment is received
2061    /// - a swap tx is confirmed
2062    ///
2063    /// This can be called when callbacks are no longer needed or the `webhook_url`
2064    /// has changed such that it needs unregistering. For example, the token is valid but the locale changes.
2065    /// To register a webhook call [BreezServices::register_webhook].
2066    pub async fn unregister_webhook(&self, webhook_url: String) -> SdkResult<()> {
2067        info!("Unregistering for webhook notifications");
2068        self.unregister_onchain_tx_notifications(&webhook_url)
2069            .await?;
2070        self.unregister_payment_notifications(webhook_url).await?;
2071        self.persister.remove_webhook_url()?;
2072        Ok(())
2073    }
2074
2075    /// Registers for lightning payment notifications. When a payment is intercepted by the LSP
2076    /// to this node, a callback will be triggered to the `webhook_url`.
2077    ///
2078    /// Note: these notifications are registered for all LSPs (active and historical) with whom
2079    /// we have a channel.
2080    async fn register_payment_notifications(&self, webhook_url: String) -> SdkResult<()> {
2081        let message = webhook_url.clone();
2082        let sign_request = SignMessageRequest { message };
2083        let sign_response = self.sign_message(sign_request).await?;
2084
2085        // Attempt register call for all relevant LSPs
2086        let mut error_found = false;
2087        for lsp_info in get_notification_lsps(
2088            self.persister.clone(),
2089            self.lsp_api.clone(),
2090            self.node_api.clone(),
2091        )
2092        .await?
2093        {
2094            let lsp_id = lsp_info.id;
2095            let res = self
2096                .lsp_api
2097                .register_payment_notifications(
2098                    lsp_id.clone(),
2099                    lsp_info.lsp_pubkey,
2100                    webhook_url.clone(),
2101                    sign_response.signature.clone(),
2102                )
2103                .await;
2104            if res.is_err() {
2105                error_found = true;
2106                warn!("Failed to register notifications for LSP {lsp_id}: {res:?}");
2107            }
2108        }
2109
2110        match error_found {
2111            true => Err(SdkError::generic(
2112                "Failed to register notifications for at least one LSP, see logs for details",
2113            )),
2114            false => Ok(()),
2115        }
2116    }
2117
2118    /// Unregisters lightning payment notifications with the current LSP for the `webhook_url`.
2119    ///
2120    /// Note: these notifications are unregistered for all LSPs (active and historical) with whom
2121    /// we have a channel.
2122    async fn unregister_payment_notifications(&self, webhook_url: String) -> SdkResult<()> {
2123        let message = webhook_url.clone();
2124        let sign_request = SignMessageRequest { message };
2125        let sign_response = self.sign_message(sign_request).await?;
2126
2127        // Attempt register call for all relevant LSPs
2128        let mut error_found = false;
2129        for lsp_info in get_notification_lsps(
2130            self.persister.clone(),
2131            self.lsp_api.clone(),
2132            self.node_api.clone(),
2133        )
2134        .await?
2135        {
2136            let lsp_id = lsp_info.id;
2137            let res = self
2138                .lsp_api
2139                .unregister_payment_notifications(
2140                    lsp_id.clone(),
2141                    lsp_info.lsp_pubkey,
2142                    webhook_url.clone(),
2143                    sign_response.signature.clone(),
2144                )
2145                .await;
2146            if res.is_err() {
2147                error_found = true;
2148                warn!("Failed to un-register notifications for LSP {lsp_id}: {res:?}");
2149            }
2150        }
2151
2152        match error_found {
2153            true => Err(SdkError::generic(
2154                "Failed to un-register notifications for at least one LSP, see logs for details",
2155            )),
2156            false => Ok(()),
2157        }
2158    }
2159
2160    /// Registers for a onchain tx notification. When a new transaction to the specified `address`
2161    /// is confirmed, a callback will be triggered to the `webhook_url`.
2162    async fn register_onchain_tx_notification(
2163        &self,
2164        address: &str,
2165        webhook_url: &str,
2166    ) -> SdkResult<()> {
2167        let url = format!("{}/api/v1/register", self.config.chainnotifier_url);
2168        let headers = HashMap::from([("Content-Type".to_string(), "application/json".to_string())]);
2169        let body = json!({
2170            "address": address,
2171            "webhook": webhook_url
2172        })
2173        .to_string();
2174        self.rest_client
2175            .post(&url, Some(headers), Some(body))
2176            .await
2177            .map(|_| ())
2178            .map_err(|e| SdkError::ServiceConnectivity {
2179                err: format!("Failed to register for tx confirmation notifications: {e}"),
2180            })
2181    }
2182
2183    /// Unregisters all onchain tx notifications for the `webhook_url`.
2184    async fn unregister_onchain_tx_notifications(&self, webhook_url: &str) -> SdkResult<()> {
2185        let url = format!("{}/api/v1/unregister", self.config.chainnotifier_url);
2186        let headers = HashMap::from([("Content-Type".to_string(), "application/json".to_string())]);
2187        let body = json!({
2188            "webhook": webhook_url
2189        })
2190        .to_string();
2191        self.rest_client
2192            .post(&url, Some(headers), Some(body))
2193            .await
2194            .map(|_| ())
2195            .map_err(|e| SdkError::ServiceConnectivity {
2196                err: format!("Failed to unregister for tx confirmation notifications: {e}"),
2197            })
2198    }
2199
2200    async fn generate_sdk_diagnostic_data(&self) -> SdkResult<Value> {
2201        let (sdk_version, sdk_git_hash) = Self::get_sdk_version();
2202        let version = format!("SDK v{sdk_version} ({sdk_git_hash})");
2203        let state = crate::serializer::value::to_value(&self.persister.get_node_state()?)?;
2204        let payments = crate::serializer::value::to_value(
2205            &self
2206                .persister
2207                .list_payments(ListPaymentsRequest::default())?,
2208        )?;
2209        let channels = crate::serializer::value::to_value(&self.persister.list_channels()?)?;
2210        let settings = crate::serializer::value::to_value(&self.persister.list_settings()?)?;
2211        let reverse_swaps = crate::serializer::value::to_value(
2212            self.persister.list_reverse_swaps().map(sanitize_vec)?,
2213        )?;
2214        let swaps = crate::serializer::value::to_value(
2215            self.btc_receive_swapper
2216                .list_swaps(ListSwapsRequest::default())
2217                .map(sanitize_vec)?,
2218        )?;
2219        let lsp_id = crate::serializer::value::to_value(&self.persister.get_lsp_id()?)?;
2220
2221        let res = json!({
2222            "version": version,
2223            "node_state": state,
2224            "payments": payments,
2225            "channels": channels,
2226            "settings": settings,
2227            "reverse_swaps": reverse_swaps,
2228            "swaps": swaps,
2229            "lsp_id": lsp_id,
2230        });
2231        Ok(res)
2232    }
2233}
2234
2235struct GlobalSdkLogger {
2236    /// SDK internal logger, which logs to file
2237    logger: env_logger::Logger,
2238    /// Optional external log listener, that can receive a stream of log statements
2239    log_listener: Option<Box<dyn log::Log>>,
2240}
2241impl log::Log for GlobalSdkLogger {
2242    fn enabled(&self, metadata: &Metadata) -> bool {
2243        metadata.level() <= log::Level::Trace
2244    }
2245
2246    fn log(&self, record: &Record) {
2247        if self.enabled(record.metadata()) {
2248            self.logger.log(record);
2249
2250            if let Some(s) = &self.log_listener.as_ref() {
2251                if s.enabled(record.metadata()) {
2252                    s.log(record);
2253                }
2254            }
2255        }
2256    }
2257
2258    fn flush(&self) {}
2259}
2260
2261/// A helper struct to configure and build BreezServices
2262struct BreezServicesBuilder {
2263    config: Config,
2264    node_api: Option<Arc<dyn NodeAPI>>,
2265    backup_transport: Option<Arc<dyn BackupTransport>>,
2266    seed: Option<Vec<u8>>,
2267    lsp_api: Option<Arc<dyn LspAPI>>,
2268    fiat_api: Option<Arc<dyn FiatAPI>>,
2269    persister: Option<Arc<SqliteStorage>>,
2270    rest_client: Option<Arc<dyn RestClient>>,
2271    support_api: Option<Arc<dyn SupportAPI>>,
2272    swapper_api: Option<Arc<dyn SwapperAPI>>,
2273    taproot_swapper_api: Option<Arc<dyn TaprootSwapperAPI>>,
2274    /// Reverse swap functionality on the Breez Server
2275    reverse_swapper_api: Option<Arc<dyn ReverseSwapperRoutingAPI>>,
2276    /// Reverse swap functionality on the 3rd party reverse swap service
2277    reverse_swap_service_api: Option<Arc<dyn ReverseSwapServiceAPI>>,
2278    buy_bitcoin_api: Option<Arc<dyn BuyBitcoinApi>>,
2279}
2280
2281#[allow(dead_code)]
2282impl BreezServicesBuilder {
2283    pub fn new(config: Config) -> BreezServicesBuilder {
2284        BreezServicesBuilder {
2285            config,
2286            node_api: None,
2287            seed: None,
2288            lsp_api: None,
2289            fiat_api: None,
2290            persister: None,
2291            rest_client: None,
2292            support_api: None,
2293            swapper_api: None,
2294            taproot_swapper_api: None,
2295            reverse_swapper_api: None,
2296            reverse_swap_service_api: None,
2297            buy_bitcoin_api: None,
2298            backup_transport: None,
2299        }
2300    }
2301
2302    pub fn node_api(&mut self, node_api: Arc<dyn NodeAPI>) -> &mut Self {
2303        self.node_api = Some(node_api);
2304        self
2305    }
2306
2307    pub fn lsp_api(&mut self, lsp_api: Arc<dyn LspAPI>) -> &mut Self {
2308        self.lsp_api = Some(lsp_api.clone());
2309        self
2310    }
2311
2312    pub fn fiat_api(&mut self, fiat_api: Arc<dyn FiatAPI>) -> &mut Self {
2313        self.fiat_api = Some(fiat_api.clone());
2314        self
2315    }
2316
2317    pub fn buy_bitcoin_api(&mut self, buy_bitcoin_api: Arc<dyn BuyBitcoinApi>) -> &mut Self {
2318        self.buy_bitcoin_api = Some(buy_bitcoin_api.clone());
2319        self
2320    }
2321
2322    pub fn persister(&mut self, persister: Arc<SqliteStorage>) -> &mut Self {
2323        self.persister = Some(persister);
2324        self
2325    }
2326
2327    pub fn support_api(&mut self, support_api: Arc<dyn SupportAPI>) -> &mut Self {
2328        self.support_api = Some(support_api.clone());
2329        self
2330    }
2331
2332    pub fn rest_client(&mut self, rest_client: Arc<dyn RestClient>) -> &mut Self {
2333        self.rest_client = Some(rest_client.clone());
2334        self
2335    }
2336
2337    pub fn swapper_api(&mut self, swapper_api: Arc<dyn SwapperAPI>) -> &mut Self {
2338        self.swapper_api = Some(swapper_api.clone());
2339        self
2340    }
2341
2342    pub fn taproot_swapper_api(&mut self, swapper_api: Arc<dyn TaprootSwapperAPI>) -> &mut Self {
2343        self.taproot_swapper_api = Some(swapper_api.clone());
2344        self
2345    }
2346
2347    pub fn reverse_swapper_api(
2348        &mut self,
2349        reverse_swapper_api: Arc<dyn ReverseSwapperRoutingAPI>,
2350    ) -> &mut Self {
2351        self.reverse_swapper_api = Some(reverse_swapper_api.clone());
2352        self
2353    }
2354
2355    pub fn reverse_swap_service_api(
2356        &mut self,
2357        reverse_swap_service_api: Arc<dyn ReverseSwapServiceAPI>,
2358    ) -> &mut Self {
2359        self.reverse_swap_service_api = Some(reverse_swap_service_api.clone());
2360        self
2361    }
2362
2363    pub fn backup_transport(&mut self, backup_transport: Arc<dyn BackupTransport>) -> &mut Self {
2364        self.backup_transport = Some(backup_transport.clone());
2365        self
2366    }
2367
2368    pub fn seed(&mut self, seed: Vec<u8>) -> &mut Self {
2369        self.seed = Some(seed);
2370        self
2371    }
2372
2373    pub async fn build(
2374        &self,
2375        restore_only: Option<bool>,
2376        event_listener: Option<Box<dyn EventListener>>,
2377    ) -> BreezServicesResult<Arc<BreezServices>> {
2378        if self.node_api.is_none() && self.seed.is_none() {
2379            return Err(ConnectError::Generic {
2380                err: "Either node_api or both credentials and seed should be provided".into(),
2381            });
2382        }
2383
2384        // The storage is implemented via sqlite.
2385        let persister = self
2386            .persister
2387            .clone()
2388            .unwrap_or_else(|| Arc::new(SqliteStorage::new(self.config.working_dir.clone())));
2389        persister.init()?;
2390
2391        let mut node_api = self.node_api.clone();
2392        let mut backup_transport = self.backup_transport.clone();
2393        if node_api.is_none() {
2394            let (node_impl, backup_transport_impl) = node_builder::build_node(
2395                self.config.clone(),
2396                self.seed.clone().unwrap(),
2397                restore_only,
2398                persister.clone(),
2399            )
2400            .await?;
2401            node_api = Some(node_impl);
2402            if backup_transport.is_none() {
2403                backup_transport = Some(backup_transport_impl);
2404            }
2405        }
2406
2407        if backup_transport.is_none() {
2408            return Err(ConnectError::Generic {
2409                err: "State synchronizer should be provided".into(),
2410            });
2411        }
2412
2413        let unwrapped_node_api = node_api.unwrap();
2414        let unwrapped_backup_transport = backup_transport.unwrap();
2415
2416        // create the backup encryption key and then the backup watcher
2417        let backup_encryption_key = unwrapped_node_api
2418            .derive_bip32_key(vec![
2419                ChildNumber::from_hardened_idx(139)?,
2420                ChildNumber::from(0),
2421            ])
2422            .await?;
2423
2424        // We calculate the legacy key as a fallback for the case where the backup is still
2425        // encrypted with the old key.
2426        let legacy_backup_encryption_key = unwrapped_node_api
2427            .legacy_derive_bip32_key(vec![
2428                ChildNumber::from_hardened_idx(139)?,
2429                ChildNumber::from(0),
2430            ])
2431            .await?;
2432        let backup_watcher = BackupWatcher::new(
2433            self.config.clone(),
2434            unwrapped_backup_transport.clone(),
2435            persister.clone(),
2436            backup_encryption_key.to_priv().to_bytes(),
2437            legacy_backup_encryption_key.to_priv().to_bytes(),
2438        );
2439
2440        // breez_server provides both FiatAPI & LspAPI implementations
2441        let breez_server = Arc::new(
2442            BreezServer::new(self.config.breezserver.clone(), self.config.api_key.clone())
2443                .map_err(|e| ConnectError::ServiceConnectivity {
2444                    err: format!("Failed to create BreezServer: {e}"),
2445                })?,
2446        );
2447
2448        // Ensure breez server connection is established in the background
2449        let cloned_breez_server = breez_server.clone();
2450        tokio::spawn(async move {
2451            if let Err(e) = cloned_breez_server.ping().await {
2452                error!("Failed to ping breez server: {e}");
2453            }
2454        });
2455
2456        let current_lsp_id = persister.get_lsp_id()?;
2457        if current_lsp_id.is_none() && self.config.default_lsp_id.is_some() {
2458            persister.set_lsp(self.config.default_lsp_id.clone().unwrap(), None)?;
2459        }
2460
2461        let payment_receiver = Arc::new(PaymentReceiver {
2462            config: self.config.clone(),
2463            node_api: unwrapped_node_api.clone(),
2464            lsp: breez_server.clone(),
2465            persister: persister.clone(),
2466        });
2467
2468        let rest_client: Arc<dyn RestClient> = match self.rest_client.clone() {
2469            Some(rest_client) => rest_client,
2470            None => Arc::new(ReqwestRestClient::new()?),
2471        };
2472
2473        // mempool space is used to monitor the chain
2474        let mempoolspace_urls = match self.config.mempoolspace_url.clone() {
2475            None => {
2476                let cached = persister.get_mempoolspace_base_urls()?;
2477                match cached.len() {
2478                    // If we have no cached values, or we cached an empty list, fetch new ones
2479                    0 => {
2480                        let fresh_urls = breez_server
2481                            .fetch_mempoolspace_urls()
2482                            .await
2483                            .unwrap_or(vec![DEFAULT_MEMPOOL_SPACE_URL.into()]);
2484                        persister.set_mempoolspace_base_urls(fresh_urls.clone())?;
2485                        fresh_urls
2486                    }
2487                    // If we already have cached values, return those
2488                    _ => cached,
2489                }
2490            }
2491            Some(mempoolspace_url_from_config) => vec![mempoolspace_url_from_config],
2492        };
2493        let chain_service = Arc::new(RedundantChainService::from_base_urls(
2494            rest_client.clone(),
2495            mempoolspace_urls,
2496        ));
2497
2498        let btc_receive_swapper = Arc::new(BTCReceiveSwap::new(BTCReceiveSwapParameters {
2499            chain_service: chain_service.clone(),
2500            payment_storage: persister.clone(),
2501            network: self.config.network.into(),
2502            node_api: unwrapped_node_api.clone(),
2503            node_state_storage: persister.clone(),
2504            payment_receiver: payment_receiver.clone(),
2505            segwit_swapper_api: self
2506                .swapper_api
2507                .clone()
2508                .unwrap_or_else(|| breez_server.clone()),
2509            swap_storage: persister.clone(),
2510            taproot_swapper_api: self
2511                .taproot_swapper_api
2512                .clone()
2513                .unwrap_or_else(|| breez_server.clone()),
2514        }));
2515
2516        let btc_send_swapper = Arc::new(BTCSendSwap::new(
2517            self.config.clone(),
2518            self.reverse_swapper_api
2519                .clone()
2520                .unwrap_or_else(|| breez_server.clone()),
2521            self.reverse_swap_service_api
2522                .clone()
2523                .unwrap_or_else(|| Arc::new(BoltzApi::new(rest_client.clone()))),
2524            persister.clone(),
2525            chain_service.clone(),
2526            unwrapped_node_api.clone(),
2527        ));
2528
2529        // create a shutdown channel (sender and receiver)
2530        let (shutdown_sender, _shutdown_receiver) = watch::channel::<()>(());
2531
2532        let buy_bitcoin_api = self
2533            .buy_bitcoin_api
2534            .clone()
2535            .unwrap_or_else(|| Arc::new(BuyBitcoinService::new(breez_server.clone())));
2536
2537        // Create the node services and it them statically
2538        let breez_services = Arc::new(BreezServices {
2539            config: self.config.clone(),
2540            started: Mutex::new(false),
2541            node_api: unwrapped_node_api.clone(),
2542            lsp_api: self.lsp_api.clone().unwrap_or_else(|| breez_server.clone()),
2543            fiat_api: self
2544                .fiat_api
2545                .clone()
2546                .unwrap_or_else(|| breez_server.clone()),
2547            support_api: self
2548                .support_api
2549                .clone()
2550                .unwrap_or_else(|| breez_server.clone()),
2551            buy_bitcoin_api,
2552            chain_service,
2553            persister: persister.clone(),
2554            rest_client,
2555            btc_receive_swapper,
2556            btc_send_swapper,
2557            payment_receiver,
2558            event_listener,
2559            backup_watcher: Arc::new(backup_watcher),
2560            shutdown_sender,
2561        });
2562
2563        Ok(breez_services)
2564    }
2565}
2566
2567/// Attempts to convert the phrase to a mnemonic, then to a seed.
2568///
2569/// If the phrase is not a valid mnemonic, an error is returned.
2570pub fn mnemonic_to_seed(phrase: String) -> Result<Vec<u8>> {
2571    let mnemonic = Mnemonic::from_phrase(&phrase, Language::English)?;
2572    let seed = Seed::new(&mnemonic, "");
2573    Ok(seed.as_bytes().to_vec())
2574}
2575
2576pub struct OpenChannelParams {
2577    pub payer_amount_msat: u64,
2578    pub opening_fee_params: models::OpeningFeeParams,
2579}
2580
2581#[tonic::async_trait]
2582pub trait Receiver: Send + Sync {
2583    fn open_channel_needed(&self, amount_msat: u64) -> Result<bool, ReceivePaymentError>;
2584    async fn receive_payment(
2585        &self,
2586        req: ReceivePaymentRequest,
2587    ) -> Result<ReceivePaymentResponse, ReceivePaymentError>;
2588    async fn wrap_node_invoice(
2589        &self,
2590        invoice: &str,
2591        params: Option<OpenChannelParams>,
2592        lsp_info: Option<LspInformation>,
2593    ) -> Result<String, ReceivePaymentError>;
2594}
2595
2596pub(crate) struct PaymentReceiver {
2597    config: Config,
2598    node_api: Arc<dyn NodeAPI>,
2599    lsp: Arc<dyn LspAPI>,
2600    persister: Arc<SqliteStorage>,
2601}
2602
2603#[tonic::async_trait]
2604impl Receiver for PaymentReceiver {
2605    fn open_channel_needed(&self, amount_msat: u64) -> Result<bool, ReceivePaymentError> {
2606        let node_state = self
2607            .persister
2608            .get_node_state()?
2609            .ok_or(ReceivePaymentError::Generic {
2610                err: "Node info not found".into(),
2611            })?;
2612        Ok(node_state.max_receivable_single_payment_amount_msat < amount_msat)
2613    }
2614
2615    async fn receive_payment(
2616        &self,
2617        req: ReceivePaymentRequest,
2618    ) -> Result<ReceivePaymentResponse, ReceivePaymentError> {
2619        let lsp_info = get_lsp(self.persister.clone(), self.lsp.clone()).await?;
2620        let expiry = req.expiry.unwrap_or(INVOICE_PAYMENT_FEE_EXPIRY_SECONDS);
2621
2622        ensure_sdk!(
2623            req.amount_msat > 0,
2624            ReceivePaymentError::InvalidAmount {
2625                err: "Receive amount must be more than 0".into()
2626            }
2627        );
2628
2629        let mut destination_invoice_amount_msat = req.amount_msat;
2630        let mut channel_opening_fee_params = None;
2631        let mut channel_fees_msat = None;
2632
2633        // check if we need to open channel
2634        let open_channel_needed = self.open_channel_needed(req.amount_msat)?;
2635        if open_channel_needed {
2636            info!("We need to open a channel");
2637
2638            // we need to open channel so we are calculating the fees for the LSP (coming either from the user, or from the LSP)
2639            let ofp = match req.opening_fee_params {
2640                Some(fee_params) => fee_params,
2641                None => lsp_info.cheapest_open_channel_fee(expiry)?.clone(),
2642            };
2643
2644            channel_opening_fee_params = Some(ofp.clone());
2645            channel_fees_msat = Some(ofp.get_channel_fees_msat_for(req.amount_msat));
2646            if let Some(channel_fees_msat) = channel_fees_msat {
2647                info!("zero-conf fee calculation option: lsp fee rate (proportional): {}:  (minimum {}), total fees for channel: {}",
2648                    ofp.proportional, ofp.min_msat, channel_fees_msat);
2649
2650                if req.amount_msat < channel_fees_msat + 1000 {
2651                    return Err(
2652                        ReceivePaymentError::InvalidAmount{err: format!(
2653                           "Amount should be more than the minimum fees {channel_fees_msat} msat, but is {} msat",
2654                            req.amount_msat
2655                        )}
2656                    );
2657                }
2658                // remove the fees from the amount to get the small amount on the current node invoice.
2659                destination_invoice_amount_msat = req.amount_msat - channel_fees_msat;
2660            }
2661        }
2662
2663        info!("Creating invoice on NodeAPI");
2664        let invoice = self
2665            .node_api
2666            .create_invoice(CreateInvoiceRequest {
2667                amount_msat: destination_invoice_amount_msat,
2668                description: req.description,
2669                payer_amount_msat: match open_channel_needed {
2670                    true => Some(req.amount_msat),
2671                    false => None,
2672                },
2673                preimage: req.preimage,
2674                use_description_hash: req.use_description_hash,
2675                expiry: Some(expiry),
2676                cltv: Some(req.cltv.unwrap_or(144)),
2677            })
2678            .await?;
2679        info!("Invoice created {}", invoice);
2680
2681        let open_channel_params = match open_channel_needed {
2682            true => Some(OpenChannelParams {
2683                payer_amount_msat: req.amount_msat,
2684                opening_fee_params: channel_opening_fee_params.clone().ok_or(
2685                    ReceivePaymentError::Generic {
2686                        err: "We need to open a channel, but no channel opening fee params found"
2687                            .into(),
2688                    },
2689                )?,
2690            }),
2691            false => None,
2692        };
2693
2694        let invoice = self
2695            .wrap_node_invoice(&invoice, open_channel_params, Some(lsp_info))
2696            .await?;
2697        let parsed_invoice = parse_invoice(&invoice)?;
2698
2699        // return the signed, converted invoice with hints
2700        Ok(ReceivePaymentResponse {
2701            ln_invoice: parsed_invoice,
2702            opening_fee_params: channel_opening_fee_params,
2703            opening_fee_msat: channel_fees_msat,
2704        })
2705    }
2706
2707    async fn wrap_node_invoice(
2708        &self,
2709        invoice: &str,
2710        params: Option<OpenChannelParams>,
2711        lsp_info: Option<LspInformation>,
2712    ) -> Result<String, ReceivePaymentError> {
2713        let lsp_info = match lsp_info {
2714            Some(lsp_info) => lsp_info,
2715            None => get_lsp(self.persister.clone(), self.lsp.clone()).await?,
2716        };
2717
2718        match params {
2719            Some(params) => {
2720                self.wrap_open_channel_invoice(invoice, params, &lsp_info)
2721                    .await
2722            }
2723            None => self.ensure_hint(invoice, &lsp_info).await,
2724        }
2725    }
2726}
2727
2728impl PaymentReceiver {
2729    async fn ensure_hint(
2730        &self,
2731        invoice: &str,
2732        lsp_info: &LspInformation,
2733    ) -> Result<String, ReceivePaymentError> {
2734        info!("Getting routing hints from node");
2735        let (mut hints, has_public_channel) = self.node_api.get_routing_hints(lsp_info).await?;
2736        if !has_public_channel && hints.is_empty() {
2737            return Err(ReceivePaymentError::InvoiceNoRoutingHints {
2738                err: "Must have at least one active channel".into(),
2739            });
2740        }
2741
2742        let parsed_invoice = parse_invoice(invoice)?;
2743
2744        // check if the lsp hint already exists
2745        info!("Existing routing hints {:?}", parsed_invoice.routing_hints);
2746
2747        // limit the hints to max 3 and extract the lsp one.
2748        if let Some(lsp_hint) = Self::limit_and_extract_lsp_hint(&mut hints, lsp_info) {
2749            if parsed_invoice.contains_hint_for_node(lsp_info.pubkey.as_str()) {
2750                return Ok(String::from(invoice));
2751            }
2752
2753            info!("Adding lsp hint: {lsp_hint:?}");
2754            let modified =
2755                add_routing_hints(invoice, true, &vec![lsp_hint], parsed_invoice.amount_msat)?;
2756
2757            let invoice = self.node_api.sign_invoice(modified).await?;
2758            info!("Signed invoice with hint = {invoice}");
2759            return Ok(invoice);
2760        }
2761
2762        if parsed_invoice.routing_hints.is_empty() {
2763            info!("Adding custom hints: {hints:?}");
2764            let modified = add_routing_hints(invoice, false, &hints, parsed_invoice.amount_msat)?;
2765            let invoice = self.node_api.sign_invoice(modified).await?;
2766            info!("Signed invoice with hints = {invoice}");
2767            return Ok(invoice);
2768        }
2769
2770        Ok(String::from(invoice))
2771    }
2772
2773    async fn wrap_open_channel_invoice(
2774        &self,
2775        invoice: &str,
2776        params: OpenChannelParams,
2777        lsp_info: &LspInformation,
2778    ) -> Result<String, ReceivePaymentError> {
2779        let parsed_invoice = parse_invoice(invoice)?;
2780        let open_channel_hint = RouteHint {
2781            hops: vec![RouteHintHop {
2782                src_node_id: lsp_info.pubkey.clone(),
2783                short_channel_id: "1x0x0".to_string(),
2784                fees_base_msat: lsp_info.base_fee_msat as u32,
2785                fees_proportional_millionths: (lsp_info.fee_rate * 1000000.0) as u32,
2786                cltv_expiry_delta: lsp_info.time_lock_delta as u64,
2787                htlc_minimum_msat: Some(lsp_info.min_htlc_msat as u64),
2788                htlc_maximum_msat: None,
2789            }],
2790        };
2791        info!("Adding open channel hint: {open_channel_hint:?}");
2792        let invoice_with_hint = add_routing_hints(
2793            invoice,
2794            false,
2795            &vec![open_channel_hint],
2796            Some(params.payer_amount_msat),
2797        )?;
2798        let signed_invoice = self.node_api.sign_invoice(invoice_with_hint).await?;
2799
2800        info!("Registering payment with LSP");
2801        let api_key = self.config.api_key.clone().unwrap_or_default();
2802        let api_key_hash = sha256::Hash::hash(api_key.as_bytes()).to_hex();
2803
2804        self.lsp
2805            .register_payment(
2806                lsp_info.id.clone(),
2807                lsp_info.lsp_pubkey.clone(),
2808                grpc::PaymentInformation {
2809                    payment_hash: hex::decode(parsed_invoice.payment_hash.clone())
2810                        .map_err(|e| anyhow!("Failed to decode hex payment hash: {e}"))?,
2811                    payment_secret: parsed_invoice.payment_secret.clone(),
2812                    destination: hex::decode(parsed_invoice.payee_pubkey.clone())
2813                        .map_err(|e| anyhow!("Failed to decode hex payee pubkey: {e}"))?,
2814                    incoming_amount_msat: params.payer_amount_msat as i64,
2815                    outgoing_amount_msat: parsed_invoice
2816                        .amount_msat
2817                        .ok_or(anyhow!("Open channel invoice must have an amount"))?
2818                        as i64,
2819                    tag: json!({ "apiKeyHash": api_key_hash }).to_string(),
2820                    opening_fee_params: Some(params.opening_fee_params.into()),
2821                },
2822            )
2823            .await?;
2824        // Make sure we save the large amount so we can deduce the fees later.
2825        self.persister.insert_open_channel_payment_info(
2826            &parsed_invoice.payment_hash,
2827            params.payer_amount_msat,
2828            &signed_invoice,
2829        )?;
2830
2831        Ok(signed_invoice)
2832    }
2833
2834    fn limit_and_extract_lsp_hint(
2835        routing_hints: &mut Vec<RouteHint>,
2836        lsp_info: &LspInformation,
2837    ) -> Option<RouteHint> {
2838        let mut lsp_hint: Option<RouteHint> = None;
2839        if let Some(lsp_index) = routing_hints.iter().position(|r| {
2840            r.hops
2841                .iter()
2842                .any(|h| h.src_node_id == lsp_info.pubkey.clone())
2843        }) {
2844            lsp_hint = Some(routing_hints.remove(lsp_index));
2845        }
2846        if routing_hints.len() > 3 {
2847            routing_hints.drain(3..);
2848        }
2849        lsp_hint
2850    }
2851}
2852
2853/// Convenience method to look up LSP info based on current LSP ID
2854async fn get_lsp(
2855    persister: Arc<SqliteStorage>,
2856    lsp_api: Arc<dyn LspAPI>,
2857) -> SdkResult<LspInformation> {
2858    let lsp_id = persister
2859        .get_lsp_id()?
2860        .ok_or(SdkError::generic("No LSP ID found"))?;
2861
2862    get_lsp_by_id(persister, lsp_api, lsp_id.as_str())
2863        .await?
2864        .ok_or_else(|| SdkError::Generic {
2865            err: format!("No LSP found for id {lsp_id}"),
2866        })
2867}
2868
2869async fn get_lsps(
2870    persister: Arc<SqliteStorage>,
2871    lsp_api: Arc<dyn LspAPI>,
2872) -> SdkResult<Vec<LspInformation>> {
2873    let node_pubkey = persister
2874        .get_node_state()?
2875        .ok_or(SdkError::generic("Node info not found"))?
2876        .id;
2877
2878    lsp_api.list_lsps(node_pubkey).await
2879}
2880
2881async fn get_lsp_by_id(
2882    persister: Arc<SqliteStorage>,
2883    lsp_api: Arc<dyn LspAPI>,
2884    lsp_id: &str,
2885) -> SdkResult<Option<LspInformation>> {
2886    Ok(get_lsps(persister, lsp_api)
2887        .await?
2888        .into_iter()
2889        .find(|lsp| lsp.id.as_str() == lsp_id))
2890}
2891
2892/// Convenience method to get all LSPs (active and historical) relevant for registering or
2893/// unregistering webhook notifications
2894async fn get_notification_lsps(
2895    persister: Arc<SqliteStorage>,
2896    lsp_api: Arc<dyn LspAPI>,
2897    node_api: Arc<dyn NodeAPI>,
2898) -> SdkResult<Vec<LspInformation>> {
2899    let node_pubkey = persister
2900        .get_node_state()?
2901        .ok_or(SdkError::generic("Node info not found"))?
2902        .id;
2903    let mut open_peers = None;
2904
2905    let mut notification_lsps = vec![];
2906    for lsp in lsp_api.list_used_lsps(node_pubkey).await? {
2907        match !lsp.opening_fee_params_list.values.is_empty() {
2908            true => {
2909                // Non-empty fee params list = this is the active LSP
2910                // Always consider the active LSP for notifications
2911                notification_lsps.push(lsp);
2912            }
2913            false => {
2914                // Consider only historical LSPs with whom we have an active channel
2915                let lsp_pubkey = hex::decode(&lsp.pubkey)
2916                    .map_err(|e| anyhow!("Failed decode lsp pubkey: {e}"))?;
2917                let open_peers = match &open_peers {
2918                    Some(open_peers) => open_peers,
2919                    None => {
2920                        open_peers = Some(node_api.get_open_peers().await?);
2921                        open_peers.as_ref().unwrap()
2922                    }
2923                };
2924                let has_active_channel_to_lsp = open_peers.contains(&lsp_pubkey);
2925                if has_active_channel_to_lsp {
2926                    notification_lsps.push(lsp);
2927                }
2928            }
2929        }
2930    }
2931    Ok(notification_lsps)
2932}
2933
2934#[cfg(test)]
2935pub(crate) mod tests {
2936    use std::collections::HashMap;
2937    use std::sync::Arc;
2938
2939    use anyhow::{anyhow, Result};
2940    use regex::Regex;
2941    use reqwest::Url;
2942    use sdk_common::prelude::Rate;
2943
2944    use crate::breez_services::{BreezServices, BreezServicesBuilder};
2945    use crate::models::{LnPaymentDetails, NodeState, Payment, PaymentDetails, PaymentTypeFilter};
2946    use crate::node_api::NodeAPI;
2947    use crate::persist::cache::NodeStateStorage;
2948    use crate::persist::swap::SwapStorage;
2949    use crate::test_utils::*;
2950    use crate::*;
2951
2952    use super::{PaymentReceiver, Receiver};
2953
2954    #[tokio::test]
2955    async fn test_node_state() -> Result<()> {
2956        // let storage_path = format!("{}/storage.sql", get_test_working_dir());
2957        // std::fs::remove_file(storage_path).ok();
2958
2959        let dummy_node_state = get_dummy_node_state();
2960
2961        let lnurl_metadata = "{'key': 'sample-metadata-val'}";
2962        let test_ln_address = "test@ln-address.com";
2963        let test_lnurl_withdraw_endpoint = "https://test.endpoint.lnurl-w";
2964        let sa = SuccessActionProcessed::Message {
2965            data: MessageSuccessActionData {
2966                message: "test message".into(),
2967            },
2968        };
2969
2970        let payment_hash_lnurl_withdraw = "2222";
2971        let payment_hash_with_lnurl_success_action = "3333";
2972        let payment_hash_swap: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8];
2973        let swap_info = SwapInfo {
2974            bitcoin_address: "123".to_string(),
2975            created_at: 12345678,
2976            lock_height: 654321,
2977            payment_hash: payment_hash_swap.clone(),
2978            preimage: vec![],
2979            private_key: vec![],
2980            public_key: vec![],
2981            swapper_public_key: vec![],
2982            script: vec![],
2983            bolt11: Some("312".into()),
2984            paid_msat: 1000,
2985            confirmed_sats: 1,
2986            unconfirmed_sats: 0,
2987            total_incoming_txs: 1,
2988            status: SwapStatus::Refundable,
2989            refund_tx_ids: vec![],
2990            unconfirmed_tx_ids: vec![],
2991            confirmed_tx_ids: vec![],
2992            min_allowed_deposit: 5_000,
2993            max_allowed_deposit: 1_000_000,
2994            max_swapper_payable: 2_000_000,
2995            last_redeem_error: None,
2996            channel_opening_fees: Some(OpeningFeeParams {
2997                min_msat: 5_000_000,
2998                proportional: 50,
2999                valid_until: "date".to_string(),
3000                max_idle_time: 12345,
3001                max_client_to_self_delay: 234,
3002                promise: "promise".to_string(),
3003            }),
3004            confirmed_at: Some(555),
3005        };
3006        let payment_hash_rev_swap: Vec<u8> = vec![8, 7, 6, 5, 4, 3, 2, 1];
3007        let preimage_rev_swap: Vec<u8> = vec![6, 6, 6, 6];
3008        let full_ref_swap_info = FullReverseSwapInfo {
3009            id: "rev_swap_id".to_string(),
3010            created_at_block_height: 0,
3011            preimage: preimage_rev_swap.clone(),
3012            private_key: vec![],
3013            claim_pubkey: "claim_pubkey".to_string(),
3014            timeout_block_height: 600_000,
3015            invoice: "645".to_string(),
3016            redeem_script: "redeem_script".to_string(),
3017            onchain_amount_sat: 250,
3018            sat_per_vbyte: Some(50),
3019            receive_amount_sat: None,
3020            cache: ReverseSwapInfoCached {
3021                status: ReverseSwapStatus::CompletedConfirmed,
3022                lockup_txid: Some("lockup_txid".to_string()),
3023                claim_txid: Some("claim_txid".to_string()),
3024            },
3025        };
3026        let rev_swap_info = ReverseSwapInfo {
3027            id: "rev_swap_id".to_string(),
3028            claim_pubkey: "claim_pubkey".to_string(),
3029            lockup_txid: Some("lockup_txid".to_string()),
3030            claim_txid: Some("claim_txid".to_string()),
3031            onchain_amount_sat: 250,
3032            status: ReverseSwapStatus::CompletedConfirmed,
3033        };
3034        let dummy_transactions = vec![
3035            Payment {
3036                id: "1111".to_string(),
3037                payment_type: PaymentType::Received,
3038                payment_time: 100000,
3039                amount_msat: 10,
3040                fee_msat: 0,
3041                status: PaymentStatus::Complete,
3042                error: None,
3043                description: Some("test receive".to_string()),
3044                details: PaymentDetails::Ln {
3045                    data: LnPaymentDetails {
3046                        payment_hash: "1111".to_string(),
3047                        label: "".to_string(),
3048                        destination_pubkey: "1111".to_string(),
3049                        payment_preimage: "2222".to_string(),
3050                        keysend: false,
3051                        bolt11: "1111".to_string(),
3052                        lnurl_success_action: None,
3053                        lnurl_pay_domain: None,
3054                        lnurl_pay_comment: None,
3055                        lnurl_metadata: None,
3056                        ln_address: None,
3057                        lnurl_withdraw_endpoint: None,
3058                        swap_info: None,
3059                        reverse_swap_info: None,
3060                        pending_expiration_block: None,
3061                        open_channel_bolt11: None,
3062                    },
3063                },
3064                metadata: None,
3065            },
3066            Payment {
3067                id: payment_hash_lnurl_withdraw.to_string(),
3068                payment_type: PaymentType::Received,
3069                payment_time: 150000,
3070                amount_msat: 10,
3071                fee_msat: 0,
3072                status: PaymentStatus::Complete,
3073                error: None,
3074                description: Some("test lnurl-withdraw receive".to_string()),
3075                details: PaymentDetails::Ln {
3076                    data: LnPaymentDetails {
3077                        payment_hash: payment_hash_lnurl_withdraw.to_string(),
3078                        label: "".to_string(),
3079                        destination_pubkey: "1111".to_string(),
3080                        payment_preimage: "3333".to_string(),
3081                        keysend: false,
3082                        bolt11: "1111".to_string(),
3083                        lnurl_success_action: None,
3084                        lnurl_pay_domain: None,
3085                        lnurl_pay_comment: None,
3086                        lnurl_metadata: None,
3087                        ln_address: None,
3088                        lnurl_withdraw_endpoint: Some(test_lnurl_withdraw_endpoint.to_string()),
3089                        swap_info: None,
3090                        reverse_swap_info: None,
3091                        pending_expiration_block: None,
3092                        open_channel_bolt11: None,
3093                    },
3094                },
3095                metadata: None,
3096            },
3097            Payment {
3098                id: payment_hash_with_lnurl_success_action.to_string(),
3099                payment_type: PaymentType::Sent,
3100                payment_time: 200000,
3101                amount_msat: 8,
3102                fee_msat: 2,
3103                status: PaymentStatus::Complete,
3104                error: None,
3105                description: Some("test payment".to_string()),
3106                details: PaymentDetails::Ln {
3107                    data: LnPaymentDetails {
3108                        payment_hash: payment_hash_with_lnurl_success_action.to_string(),
3109                        label: "".to_string(),
3110                        destination_pubkey: "123".to_string(),
3111                        payment_preimage: "4444".to_string(),
3112                        keysend: false,
3113                        bolt11: "123".to_string(),
3114                        lnurl_success_action: Some(sa.clone()),
3115                        lnurl_pay_domain: None,
3116                        lnurl_pay_comment: None,
3117                        lnurl_metadata: Some(lnurl_metadata.to_string()),
3118                        ln_address: Some(test_ln_address.to_string()),
3119                        lnurl_withdraw_endpoint: None,
3120                        swap_info: None,
3121                        reverse_swap_info: None,
3122                        pending_expiration_block: None,
3123                        open_channel_bolt11: None,
3124                    },
3125                },
3126                metadata: None,
3127            },
3128            Payment {
3129                id: hex::encode(payment_hash_swap.clone()),
3130                payment_type: PaymentType::Received,
3131                payment_time: 250000,
3132                amount_msat: 1_000,
3133                fee_msat: 0,
3134                status: PaymentStatus::Complete,
3135                error: None,
3136                description: Some("test receive".to_string()),
3137                details: PaymentDetails::Ln {
3138                    data: LnPaymentDetails {
3139                        payment_hash: hex::encode(payment_hash_swap),
3140                        label: "".to_string(),
3141                        destination_pubkey: "321".to_string(),
3142                        payment_preimage: "5555".to_string(),
3143                        keysend: false,
3144                        bolt11: "312".to_string(),
3145                        lnurl_success_action: None,
3146                        lnurl_pay_domain: None,
3147                        lnurl_pay_comment: None,
3148                        lnurl_metadata: None,
3149                        ln_address: None,
3150                        lnurl_withdraw_endpoint: None,
3151                        swap_info: Some(swap_info.clone()),
3152                        reverse_swap_info: None,
3153                        pending_expiration_block: None,
3154                        open_channel_bolt11: None,
3155                    },
3156                },
3157                metadata: None,
3158            },
3159            Payment {
3160                id: hex::encode(payment_hash_rev_swap.clone()),
3161                payment_type: PaymentType::Sent,
3162                payment_time: 300000,
3163                amount_msat: 50_000_000,
3164                fee_msat: 2_000,
3165                status: PaymentStatus::Complete,
3166                error: None,
3167                description: Some("test send onchain".to_string()),
3168                details: PaymentDetails::Ln {
3169                    data: LnPaymentDetails {
3170                        payment_hash: hex::encode(payment_hash_rev_swap),
3171                        label: "".to_string(),
3172                        destination_pubkey: "321".to_string(),
3173                        payment_preimage: hex::encode(preimage_rev_swap),
3174                        keysend: false,
3175                        bolt11: "312".to_string(),
3176                        lnurl_success_action: None,
3177                        lnurl_metadata: None,
3178                        lnurl_pay_domain: None,
3179                        lnurl_pay_comment: None,
3180                        ln_address: None,
3181                        lnurl_withdraw_endpoint: None,
3182                        swap_info: None,
3183                        reverse_swap_info: Some(rev_swap_info.clone()),
3184                        pending_expiration_block: None,
3185                        open_channel_bolt11: None,
3186                    },
3187                },
3188                metadata: None,
3189            },
3190        ];
3191        let node_api = Arc::new(MockNodeAPI::new(dummy_node_state.clone()));
3192
3193        let test_config = create_test_config();
3194        let persister = Arc::new(create_test_persister(test_config.clone()));
3195        persister.init()?;
3196        persister.insert_or_update_payments(&dummy_transactions, false)?;
3197        persister.insert_payment_external_info(
3198            payment_hash_with_lnurl_success_action,
3199            PaymentExternalInfo {
3200                lnurl_pay_success_action: Some(sa.clone()),
3201                lnurl_pay_domain: None,
3202                lnurl_pay_comment: None,
3203                lnurl_metadata: Some(lnurl_metadata.to_string()),
3204                ln_address: Some(test_ln_address.to_string()),
3205                lnurl_withdraw_endpoint: None,
3206                attempted_amount_msat: None,
3207                attempted_error: None,
3208            },
3209        )?;
3210        persister.insert_payment_external_info(
3211            payment_hash_lnurl_withdraw,
3212            PaymentExternalInfo {
3213                lnurl_pay_success_action: None,
3214                lnurl_pay_domain: None,
3215                lnurl_pay_comment: None,
3216                lnurl_metadata: None,
3217                ln_address: None,
3218                lnurl_withdraw_endpoint: Some(test_lnurl_withdraw_endpoint.to_string()),
3219                attempted_amount_msat: None,
3220                attempted_error: None,
3221            },
3222        )?;
3223        persister.insert_swap(&swap_info)?;
3224        persister.update_swap_bolt11(
3225            swap_info.bitcoin_address.clone(),
3226            swap_info.bolt11.clone().unwrap(),
3227        )?;
3228        persister.insert_reverse_swap(&full_ref_swap_info)?;
3229        persister
3230            .update_reverse_swap_status("rev_swap_id", &ReverseSwapStatus::CompletedConfirmed)?;
3231        persister
3232            .update_reverse_swap_lockup_txid("rev_swap_id", Some("lockup_txid".to_string()))?;
3233        persister.update_reverse_swap_claim_txid("rev_swap_id", Some("claim_txid".to_string()))?;
3234
3235        let mut builder = BreezServicesBuilder::new(test_config.clone());
3236        let breez_services = builder
3237            .lsp_api(Arc::new(MockBreezServer {}))
3238            .fiat_api(Arc::new(MockBreezServer {}))
3239            .node_api(node_api)
3240            .persister(persister)
3241            .backup_transport(Arc::new(MockBackupTransport::new()))
3242            .build(None, None)
3243            .await?;
3244
3245        breez_services.sync().await?;
3246        let fetched_state = breez_services.node_info()?;
3247        assert_eq!(fetched_state, dummy_node_state);
3248
3249        let all = breez_services
3250            .list_payments(ListPaymentsRequest::default())
3251            .await?;
3252        let mut cloned = all.clone();
3253
3254        // test the right order
3255        cloned.reverse();
3256        assert_eq!(dummy_transactions, cloned);
3257
3258        let received = breez_services
3259            .list_payments(ListPaymentsRequest {
3260                filters: Some(vec![PaymentTypeFilter::Received]),
3261                ..Default::default()
3262            })
3263            .await?;
3264        assert_eq!(
3265            received,
3266            vec![cloned[3].clone(), cloned[1].clone(), cloned[0].clone()]
3267        );
3268
3269        let sent = breez_services
3270            .list_payments(ListPaymentsRequest {
3271                filters: Some(vec![
3272                    PaymentTypeFilter::Sent,
3273                    PaymentTypeFilter::ClosedChannel,
3274                ]),
3275                ..Default::default()
3276            })
3277            .await?;
3278        assert_eq!(sent, vec![cloned[4].clone(), cloned[2].clone()]);
3279        assert!(matches!(
3280                &sent[1].details,
3281                PaymentDetails::Ln {data: LnPaymentDetails {lnurl_success_action, ..}}
3282                if lnurl_success_action == &Some(sa)));
3283        assert!(matches!(
3284                &sent[1].details,
3285                PaymentDetails::Ln {data: LnPaymentDetails {lnurl_pay_domain, ln_address, ..}}
3286                if lnurl_pay_domain.is_none() && ln_address == &Some(test_ln_address.to_string())));
3287        assert!(matches!(
3288                &received[1].details,
3289                PaymentDetails::Ln {data: LnPaymentDetails {lnurl_withdraw_endpoint, ..}}
3290                if lnurl_withdraw_endpoint == &Some(test_lnurl_withdraw_endpoint.to_string())));
3291        assert!(matches!(
3292                &received[0].details,
3293                PaymentDetails::Ln {data: LnPaymentDetails {swap_info: swap, ..}}
3294                if swap == &Some(swap_info)));
3295        assert!(matches!(
3296                &sent[0].details,
3297                PaymentDetails::Ln {data: LnPaymentDetails {reverse_swap_info: rev_swap, ..}}
3298                if rev_swap == &Some(rev_swap_info)));
3299
3300        Ok(())
3301    }
3302
3303    #[tokio::test]
3304    async fn test_receive_with_open_channel() -> Result<()> {
3305        let config = create_test_config();
3306        let persister = Arc::new(create_test_persister(config.clone()));
3307        persister.init().unwrap();
3308
3309        let dummy_node_state = get_dummy_node_state();
3310
3311        let node_api = Arc::new(MockNodeAPI::new(dummy_node_state.clone()));
3312
3313        let breez_server = Arc::new(MockBreezServer {});
3314        persister.set_lsp(breez_server.lsp_id(), None).unwrap();
3315        persister.set_node_state(&dummy_node_state).unwrap();
3316
3317        let receiver: Arc<dyn Receiver> = Arc::new(PaymentReceiver {
3318            config,
3319            node_api,
3320            persister,
3321            lsp: breez_server.clone(),
3322        });
3323        let ln_invoice = receiver
3324            .receive_payment(ReceivePaymentRequest {
3325                amount_msat: 3_000_000,
3326                description: "should populate lsp hints".to_string(),
3327                use_description_hash: Some(false),
3328                ..Default::default()
3329            })
3330            .await?
3331            .ln_invoice;
3332        assert_eq!(ln_invoice.routing_hints[0].hops.len(), 1);
3333        let lsp_hop = &ln_invoice.routing_hints[0].hops[0];
3334        assert_eq!(lsp_hop.src_node_id, breez_server.clone().lsp_pub_key());
3335        assert_eq!(lsp_hop.short_channel_id, "1x0x0");
3336        Ok(())
3337    }
3338
3339    #[tokio::test]
3340    async fn test_list_lsps() -> Result<()> {
3341        let storage_path = format!("{}/storage.sql", get_test_working_dir());
3342        std::fs::remove_file(storage_path).ok();
3343
3344        let breez_services = breez_services()
3345            .await
3346            .map_err(|e| anyhow!("Failed to get the BreezServices: {e}"))?;
3347        breez_services.sync().await?;
3348
3349        let node_pubkey = breez_services.node_info()?.id;
3350        let lsps = breez_services.lsp_api.list_lsps(node_pubkey).await?;
3351        assert_eq!(lsps.len(), 1);
3352
3353        Ok(())
3354    }
3355
3356    #[tokio::test]
3357    async fn test_fetch_rates() -> Result<(), Box<dyn std::error::Error>> {
3358        let breez_services = breez_services().await?;
3359        breez_services.sync().await?;
3360
3361        let rates = breez_services.fiat_api.fetch_fiat_rates().await?;
3362        assert_eq!(rates.len(), 1);
3363        assert_eq!(
3364            rates[0],
3365            Rate {
3366                coin: "USD".to_string(),
3367                value: 20_000.00,
3368            }
3369        );
3370
3371        Ok(())
3372    }
3373
3374    #[tokio::test]
3375    async fn test_buy_bitcoin_with_moonpay() -> Result<(), Box<dyn std::error::Error>> {
3376        let mock_rest_client = MockRestClient::new();
3377        mock_rest_client.add_response(MockResponse::new(200, "800000".to_string()));
3378        let rest_client: Arc<dyn RestClient> = Arc::new(mock_rest_client);
3379
3380        let breez_services = breez_services_with(None, Some(rest_client.clone()), vec![]).await?;
3381        breez_services.sync().await?;
3382
3383        let moonpay_url = breez_services
3384            .buy_bitcoin(BuyBitcoinRequest {
3385                provider: BuyBitcoinProvider::Moonpay,
3386                opening_fee_params: None,
3387                redirect_url: None,
3388            })
3389            .await?
3390            .url;
3391        let parsed = Url::parse(&moonpay_url)?;
3392        let query_pairs = parsed.query_pairs().into_owned().collect::<HashMap<_, _>>();
3393
3394        assert_eq!(parsed.host_str(), Some("mock.moonpay"));
3395        assert_eq!(parsed.path(), "/");
3396
3397        let wallet_address =
3398            parse_with_rest_client(rest_client.as_ref(), query_pairs.get("wa").unwrap(), None)
3399                .await?;
3400        assert!(matches!(wallet_address, InputType::BitcoinAddress { .. }));
3401
3402        let max_amount = query_pairs.get("ma").unwrap();
3403        assert!(Regex::new(r"^\d+\.\d{8}$").unwrap().is_match(max_amount));
3404
3405        Ok(())
3406    }
3407
3408    /// Build node service for tests
3409    pub(crate) async fn breez_services() -> Result<Arc<BreezServices>> {
3410        breez_services_with(None, None, vec![]).await
3411    }
3412
3413    /// Build node service for tests with a list of known payments
3414    pub(crate) async fn breez_services_with(
3415        node_api: Option<Arc<dyn NodeAPI>>,
3416        rest_client: Option<Arc<dyn RestClient>>,
3417        known_payments: Vec<Payment>,
3418    ) -> Result<Arc<BreezServices>> {
3419        let node_api =
3420            node_api.unwrap_or_else(|| Arc::new(MockNodeAPI::new(get_dummy_node_state())));
3421        let rest_client: Arc<dyn RestClient> =
3422            rest_client.unwrap_or_else(|| Arc::new(MockRestClient::new()));
3423
3424        let test_config = create_test_config();
3425        let persister = Arc::new(create_test_persister(test_config.clone()));
3426        persister.init()?;
3427        persister.insert_or_update_payments(&known_payments, false)?;
3428        persister.set_lsp(MockBreezServer {}.lsp_id(), None)?;
3429
3430        let mut builder = BreezServicesBuilder::new(test_config.clone());
3431        let breez_services = builder
3432            .lsp_api(Arc::new(MockBreezServer {}))
3433            .fiat_api(Arc::new(MockBreezServer {}))
3434            .taproot_swapper_api(Arc::new(MockBreezServer {}))
3435            .reverse_swap_service_api(Arc::new(MockReverseSwapperAPI {}))
3436            .buy_bitcoin_api(Arc::new(MockBuyBitcoinService {}))
3437            .persister(persister)
3438            .node_api(node_api)
3439            .rest_client(rest_client)
3440            .backup_transport(Arc::new(MockBackupTransport::new()))
3441            .build(None, None)
3442            .await?;
3443
3444        Ok(breez_services)
3445    }
3446
3447    /// Build dummy NodeState for tests
3448    pub(crate) fn get_dummy_node_state() -> NodeState {
3449        NodeState {
3450            id: "tx1".to_string(),
3451            block_height: 1,
3452            channels_balance_msat: 100,
3453            onchain_balance_msat: 1_000,
3454            pending_onchain_balance_msat: 100,
3455            utxos: vec![],
3456            max_payable_msat: 95,
3457            max_receivable_msat: 4_000_000_000,
3458            max_single_payment_amount_msat: 1_000,
3459            max_chan_reserve_msats: 0,
3460            connected_peers: vec!["1111".to_string()],
3461            max_receivable_single_payment_amount_msat: 2_000,
3462            total_inbound_liquidity_msats: 10_000,
3463        }
3464    }
3465}