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