breez_sdk_core/
breez_services.rs

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