lwk/
lightning.rs

1use std::{
2    collections::HashMap,
3    ops::ControlFlow,
4    sync::{Arc, Mutex},
5    time::Duration,
6};
7
8use crate::{
9    blockdata::address::BitcoinAddress, Address, Bolt11Invoice, ElectrumClient, EsploraClient,
10    LightningPayment, LwkError, Mnemonic, Network,
11};
12use log::{Level, Metadata, Record};
13use lwk_boltz::{
14    ChainSwapDataSerializable, ChainSwapStates, InvoiceDataSerializable,
15    PreparePayDataSerializable, RevSwapStates, SubSwapStates,
16};
17use std::fmt;
18
19/// Log level for logging messages
20#[derive(uniffi::Enum)]
21pub enum LogLevel {
22    /// Debug level
23    Debug,
24    /// Info level
25    Info,
26    /// Warning level
27    Warn,
28    /// Error level
29    Error,
30}
31
32/// An exported trait for handling logging messages.
33///
34/// Implement this trait to receive and handle logging messages from the lightning session.
35#[uniffi::export(with_foreign)]
36pub trait Logging: Send + Sync {
37    /// Log a message with the given level
38    fn log(&self, level: LogLevel, message: String);
39}
40
41/// An object to define logging at the caller level
42#[derive(uniffi::Object)]
43pub struct LoggingLink {
44    #[allow(dead_code)]
45    pub(crate) inner: Arc<dyn Logging>,
46}
47
48#[uniffi::export]
49impl LoggingLink {
50    /// Create a new `LoggingLink`
51    #[uniffi::constructor]
52    pub fn new(logging: Arc<dyn Logging>) -> Self {
53        Self { inner: logging }
54    }
55}
56
57/// Bridge logger that forwards log messages to our custom Logging trait
58struct LoggingBridge {
59    inner: Arc<dyn Logging>,
60}
61
62impl log::Log for LoggingBridge {
63    fn enabled(&self, _metadata: &Metadata) -> bool {
64        true
65    }
66
67    fn log(&self, record: &Record) {
68        let level = match record.level() {
69            Level::Error => LogLevel::Error,
70            Level::Warn => LogLevel::Warn,
71            Level::Info => LogLevel::Info,
72            Level::Debug => LogLevel::Debug,
73            Level::Trace => LogLevel::Debug, // Map Trace to Debug
74        };
75
76        let message = format!("{}", record.args());
77        self.inner.log(level, message);
78    }
79
80    fn flush(&self) {}
81}
82
83/// A builder for the `BoltzSession`
84#[derive(uniffi::Record)]
85pub struct BoltzSessionBuilder {
86    network: Arc<Network>,
87    client: Arc<AnyClient>,
88    #[uniffi(default = None)]
89    timeout: Option<u64>,
90    #[uniffi(default = None)]
91    mnemonic: Option<Arc<Mnemonic>>,
92    #[uniffi(default = None)]
93    logging: Option<Arc<dyn Logging>>,
94    #[uniffi(default = false)]
95    polling: bool,
96    #[uniffi(default = None)]
97    timeout_advance: Option<u64>,
98    #[uniffi(default = None)]
99    next_index_to_use: Option<u32>,
100    #[uniffi(default = None)]
101    referral_id: Option<String>,
102    #[uniffi(default = None)]
103    bitcoin_electrum_client_url: Option<String>,
104    #[uniffi(default = false)]
105    random_preimages: bool,
106}
107
108/// A session to pay and receive lightning payments.
109///
110/// Lightning payments are done via LBTC swaps using Boltz.
111///
112/// See `BoltzSessionBuilder` for various options to configure the session.
113#[derive(uniffi::Object)]
114pub struct BoltzSession {
115    inner: lwk_boltz::blocking::BoltzSession,
116    #[allow(dead_code)]
117    logging: Option<Arc<dyn Logging>>,
118}
119
120#[derive(uniffi::Object)]
121pub struct PreparePayResponse {
122    /// Using Option to allow consuming the inner value when complete_pay is called
123    inner: Mutex<Option<lwk_boltz::blocking::PreparePayResponse>>,
124}
125
126#[derive(uniffi::Object)]
127pub struct WebHook {
128    url: String,
129    status: Vec<String>,
130}
131
132#[derive(uniffi::Object)]
133pub struct InvoiceResponse {
134    /// Using Option to allow consuming the inner value when complete_pay is called
135    inner: Mutex<Option<lwk_boltz::blocking::InvoiceResponse>>,
136}
137
138#[derive(uniffi::Object)]
139#[uniffi::export(Display)]
140pub struct SwapList {
141    inner: Vec<lwk_boltz::SwapRestoreResponse>,
142}
143
144#[derive(uniffi::Object)]
145pub struct LockupResponse {
146    inner: Mutex<Option<lwk_boltz::blocking::LockupResponse>>,
147}
148
149impl fmt::Display for SwapList {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        let json = serde_json::to_string(&self.inner).map_err(|_| fmt::Error)?;
152        write!(f, "{json}")
153    }
154}
155
156#[derive(uniffi::Enum)]
157pub enum PaymentState {
158    Continue,
159    Success,
160    Failed,
161}
162
163#[derive(uniffi::Object)]
164pub enum AnyClient {
165    Electrum(Arc<ElectrumClient>),
166    Esplora(Arc<EsploraClient>),
167}
168
169#[uniffi::export]
170impl AnyClient {
171    #[uniffi::constructor]
172    pub fn from_electrum(client: Arc<ElectrumClient>) -> Self {
173        AnyClient::Electrum(client)
174    }
175
176    #[uniffi::constructor]
177    pub fn from_esplora(client: Arc<EsploraClient>) -> Self {
178        AnyClient::Esplora(client)
179    }
180}
181
182#[uniffi::export]
183impl BoltzSession {
184    /// Create the lightning session with default settings
185    ///
186    /// This uses default timeout and generates a random mnemonic.
187    /// For custom configuration, use [`BoltzSession::from_builder()`] instead.
188    #[uniffi::constructor]
189    pub fn new(network: &Network, client: &AnyClient) -> Result<Self, LwkError> {
190        let client_arc = match client {
191            AnyClient::Electrum(c) => Arc::new(AnyClient::Electrum(c.clone())),
192            AnyClient::Esplora(c) => Arc::new(AnyClient::Esplora(c.clone())),
193        };
194        let builder = BoltzSessionBuilder {
195            network: Arc::new(*network),
196            client: client_arc,
197            timeout: None,
198            mnemonic: None,
199            logging: None,
200            polling: false,
201            timeout_advance: None,
202            next_index_to_use: None,
203            referral_id: None,
204            bitcoin_electrum_client_url: None,
205            random_preimages: false,
206        };
207        Self::from_builder(builder)
208    }
209
210    /// Create the lightning session from a builder
211    #[uniffi::constructor]
212    pub fn from_builder(builder: BoltzSessionBuilder) -> Result<Self, LwkError> {
213        // Validate the logger by attempting a test call
214        if let Some(ref logger_impl) = builder.logging {
215            // Test the logger with a dummy message to catch issues early
216            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
217                logger_impl.log(LogLevel::Debug, "Logger validation test".to_string());
218            })).map_err(|_| LwkError::Generic {
219                msg: "Logger validation failed. Please ensure you pass an instance of a class that implements the Logging trait, not the class itself.".to_string(),
220            })?;
221        }
222
223        // Set up the custom logger if provided
224        if let Some(ref logger_impl) = builder.logging {
225            let bridge = LoggingBridge {
226                inner: logger_impl.clone(),
227            };
228            // Try to set the logger. This can only be done once globally.
229            // If it fails (logger already set), we silently continue.
230            let _ = log::set_boxed_logger(Box::new(bridge))
231                .map(|()| log::set_max_level(log::LevelFilter::Trace));
232        }
233        log::info!("Creating lightning session from builder");
234
235        let network_value = builder.network.as_ref().into();
236
237        let client = match builder.client.as_ref() {
238            AnyClient::Electrum(client) => {
239                let boltz_client = lwk_boltz::clients::ElectrumClient::from_client(
240                    client.clone_client().expect("TODO"),
241                    network_value,
242                );
243                lwk_boltz::clients::AnyClient::Electrum(Arc::new(boltz_client))
244            }
245            AnyClient::Esplora(client) => {
246                let boltz_client = lwk_boltz::clients::EsploraClient::from_client(
247                    Arc::new(client.clone_async_client().expect("TODO")),
248                    network_value,
249                );
250                lwk_boltz::clients::AnyClient::Esplora(Arc::new(boltz_client))
251            }
252        };
253
254        let mut lwk_builder = lwk_boltz::BoltzSession::builder(network_value, client);
255        if let Some(timeout_secs) = builder.timeout {
256            lwk_builder = lwk_builder.create_swap_timeout(Duration::from_secs(timeout_secs));
257        }
258        if let Some(mnemonic) = builder.mnemonic {
259            lwk_builder = lwk_builder.mnemonic(mnemonic.inner());
260        }
261        lwk_builder = lwk_builder.polling(builder.polling);
262        if let Some(timeout_advance_secs) = builder.timeout_advance {
263            lwk_builder = lwk_builder.timeout_advance(Duration::from_secs(timeout_advance_secs));
264        }
265        if let Some(next_index_to_use) = builder.next_index_to_use {
266            lwk_builder = lwk_builder.next_index_to_use(next_index_to_use);
267        }
268        if let Some(referral_id) = builder.referral_id {
269            lwk_builder = lwk_builder.referral_id(referral_id);
270        }
271        lwk_builder = lwk_builder.random_preimages(builder.random_preimages);
272
273        let inner = lwk_builder
274            .build_blocking()
275            .map_err(|e| LwkError::Generic {
276                msg: format!("Failed to create blocking lightning session: {e:?}"),
277            })?;
278        Ok(Self {
279            inner,
280            logging: builder.logging,
281        })
282    }
283
284    /// Prepare to pay a bolt11 invoice
285    pub fn prepare_pay(
286        &self,
287        lightning_payment: &LightningPayment,
288        refund_address: &Address,
289        webhook: Option<Arc<WebHook>>,
290    ) -> Result<PreparePayResponse, LwkError> {
291        let status = webhook
292            .as_ref()
293            .filter(|w| !w.status.is_empty())
294            .map(|w| {
295                w.status
296                    .iter()
297                    .map(|s| s.parse::<SubSwapStates>())
298                    .collect::<Result<Vec<_>, _>>()
299            })
300            .transpose()
301            .map_err(|_| LwkError::Generic {
302                msg: "Invalid status".to_string(),
303            })?;
304        let webhook = webhook
305            .as_ref()
306            .map(|w| lwk_boltz::Webhook::<SubSwapStates> {
307                url: w.url.to_string(),
308                hash_swap_id: None,
309                status,
310            });
311        let response =
312            self.inner
313                .prepare_pay(lightning_payment.as_ref(), refund_address.as_ref(), webhook)?;
314
315        Ok(PreparePayResponse {
316            inner: Mutex::new(Some(response)),
317        })
318    }
319
320    /// Restore a payment from its serialized data see `PreparePayResponse::serialize`
321    pub fn restore_prepare_pay(&self, data: &str) -> Result<PreparePayResponse, LwkError> {
322        let data = PreparePayDataSerializable::deserialize(data)?;
323        let response = self.inner.restore_prepare_pay(data)?;
324        Ok(PreparePayResponse {
325            inner: Mutex::new(Some(response)),
326        })
327    }
328
329    /// Create a new invoice for a given amount and a claim address to receive the payment
330    pub fn invoice(
331        &self,
332        amount: u64,
333        description: Option<String>,
334        claim_address: &Address,
335        webhook: Option<Arc<WebHook>>,
336    ) -> Result<InvoiceResponse, LwkError> {
337        let status = webhook
338            .as_ref()
339            .filter(|w| !w.status.is_empty())
340            .map(|w| {
341                w.status
342                    .iter()
343                    .map(|s| s.parse::<RevSwapStates>())
344                    .collect::<Result<Vec<_>, _>>()
345            })
346            .transpose()
347            .map_err(|_| LwkError::Generic {
348                msg: "Invalid status".to_string(),
349            })?;
350        let webhook = webhook
351            .as_ref()
352            .map(|w| lwk_boltz::Webhook::<RevSwapStates> {
353                url: w.url.to_string(),
354                hash_swap_id: None,
355                status,
356            });
357        let response = self
358            .inner
359            .invoice(amount, description, claim_address.as_ref(), webhook)
360            .map_err(|e| LwkError::Generic {
361                msg: format!("Invoice failed: {e:?}"),
362            })?;
363
364        Ok(InvoiceResponse {
365            inner: Mutex::new(Some(response)),
366        })
367    }
368
369    /// Restore an invoice flow from its serialized data see `InvoiceResponse::serialize`
370    pub fn restore_invoice(&self, data: &str) -> Result<InvoiceResponse, LwkError> {
371        let data: InvoiceDataSerializable = serde_json::from_str(data)?;
372        let response = self.inner.restore_invoice(data)?;
373        Ok(InvoiceResponse {
374            inner: Mutex::new(Some(response)),
375        })
376    }
377
378    /// Create an onchain swap to convert BTC to LBTC
379    pub fn btc_to_lbtc(
380        &self,
381        amount: u64,
382        refund_address: &BitcoinAddress,
383        claim_address: &Address,
384        webhook: Option<Arc<WebHook>>,
385    ) -> Result<LockupResponse, LwkError> {
386        let webhook = webhook
387            .as_ref()
388            .map(|w| lwk_boltz::Webhook::<ChainSwapStates> {
389                url: w.url.to_string(),
390                hash_swap_id: None,
391                status: None,
392            });
393        let response = self.inner.btc_to_lbtc(
394            amount,
395            refund_address.as_ref(),
396            claim_address.as_ref(),
397            webhook,
398        )?;
399        Ok(LockupResponse {
400            inner: Mutex::new(Some(response)),
401        })
402    }
403
404    /// Create an onchain swap to convert LBTC to BTC
405    pub fn lbtc_to_btc(
406        &self,
407        amount: u64,
408        refund_address: &Address,
409        claim_address: &BitcoinAddress,
410        webhook: Option<Arc<WebHook>>,
411    ) -> Result<LockupResponse, LwkError> {
412        let webhook = webhook
413            .as_ref()
414            .map(|w| lwk_boltz::Webhook::<ChainSwapStates> {
415                url: w.url.to_string(),
416                hash_swap_id: None,
417                status: None,
418            });
419
420        let response = self.inner.lbtc_to_btc(
421            amount,
422            refund_address.as_ref(),
423            claim_address.as_ref(),
424            webhook,
425        )?;
426        Ok(LockupResponse {
427            inner: Mutex::new(Some(response)),
428        })
429    }
430
431    /// Restore an onchain swap from its serialized data see `LockupResponse::serialize`
432    pub fn restore_lockup(&self, data: &str) -> Result<LockupResponse, LwkError> {
433        let data = ChainSwapDataSerializable::deserialize(data)?;
434        let response = self.inner.restore_lockup(data)?;
435        Ok(LockupResponse {
436            inner: Mutex::new(Some(response)),
437        })
438    }
439
440    /// Generate a rescue file with lightning session mnemonic.
441    ///
442    /// The rescue file is a JSON file that contains the swaps mnemonic.
443    /// It can be used on the Boltz web app to bring non terminated swaps to completition.
444    pub fn rescue_file(&self) -> Result<String, LwkError> {
445        let rescue_file = self.inner.rescue_file();
446        let rescue_file_json = serde_json::to_string(&rescue_file)?;
447        Ok(rescue_file_json)
448    }
449
450    /// Returns a the list of all the swaps ever done with the session mnemonic.
451    ///
452    /// The object returned can be converted to a json String with toString()
453    pub fn swap_restore(&self) -> Result<SwapList, LwkError> {
454        let response = self.inner.swap_restore()?;
455        Ok(SwapList { inner: response })
456    }
457
458    /// Filter the swap list to only include restorable reverse swaps
459    pub fn restorable_reverse_swaps(
460        &self,
461        swap_list: &SwapList,
462        claim_address: &Address,
463    ) -> Result<Vec<String>, LwkError> {
464        let response = self
465            .inner
466            .restorable_reverse_swaps(&swap_list.inner, claim_address.as_ref())?;
467        let data = response
468            .into_iter()
469            .map(|e| self.inner.restore_invoice(e.into()))
470            .map(|e| e.and_then(|e| e.serialize()))
471            .collect::<Result<Vec<_>, _>>()?;
472
473        Ok(data)
474    }
475
476    /// Filter the swap list to only include restorable submarine swaps
477    pub fn restorable_submarine_swaps(
478        &self,
479        swap_list: &SwapList,
480        refund_address: &Address,
481    ) -> Result<Vec<String>, LwkError> {
482        let response = self
483            .inner
484            .restorable_submarine_swaps(&swap_list.inner, refund_address.as_ref())?;
485        let data = response
486            .into_iter()
487            .map(|e| self.inner.restore_prepare_pay(e.into()))
488            .map(|e| e.and_then(|e| e.serialize()))
489            .collect::<Result<Vec<_>, _>>()?;
490        Ok(data)
491    }
492
493    /// Filter the swap list to only include restorable BTC to LBTC swaps
494    pub fn restorable_btc_to_lbtc_swaps(
495        &self,
496        swap_list: &SwapList,
497        claim_address: &Address,
498        refund_address: &BitcoinAddress,
499    ) -> Result<Vec<String>, LwkError> {
500        let response = self.inner.restorable_btc_to_lbtc_swaps(
501            &swap_list.inner,
502            claim_address.as_ref(),
503            refund_address.as_ref(),
504        )?;
505        let data = response
506            .into_iter()
507            .map(|e| self.inner.restore_lockup(e.into()))
508            .map(|e| e.and_then(|e| e.serialize()))
509            .collect::<Result<Vec<_>, _>>()?;
510        Ok(data)
511    }
512
513    /// Filter the swap list to only include restorable LBTC to BTC swaps
514    pub fn restorable_lbtc_to_btc_swaps(
515        &self,
516        swap_list: &SwapList,
517        claim_address: &BitcoinAddress,
518        refund_address: &Address,
519    ) -> Result<Vec<String>, LwkError> {
520        let response = self.inner.restorable_lbtc_to_btc_swaps(
521            &swap_list.inner,
522            claim_address.as_ref(),
523            refund_address.as_ref(),
524        )?;
525        let data = response
526            .into_iter()
527            .map(|e| self.inner.restore_lockup(e.into()))
528            .map(|e| e.and_then(|e| e.serialize()))
529            .collect::<Result<Vec<_>, _>>()?;
530        Ok(data)
531    }
532
533    /// Fetch informations, such as min and max amounts, about the reverse and submarine pairs from the boltz api.
534    pub fn fetch_swaps_info(&self) -> Result<String, LwkError> {
535        let (reverse, submarine, chain) = self.inner.fetch_swaps_info()?;
536        let reverse_json = serde_json::to_value(&reverse)?;
537        let submarine_json = serde_json::to_value(&submarine)?;
538        let chain_json = serde_json::to_value(&chain)?;
539        let mut result = HashMap::new();
540        result.insert("reverse".to_string(), reverse_json);
541        result.insert("submarine".to_string(), submarine_json);
542        result.insert("chain".to_string(), chain_json);
543        let result_json = serde_json::to_string(&result)?;
544        Ok(result_json)
545    }
546
547    /// Get the next index to use for deriving keypairs
548    pub fn next_index_to_use(&self) -> u32 {
549        self.inner.next_index_to_use()
550    }
551
552    /// Set the next index to use for deriving keypairs
553    ///
554    /// This may be necessary to handle multiple sessions with the same mnemonic.
555    pub fn set_next_index_to_use(&self, next_index_to_use: u32) {
556        self.inner.set_next_index_to_use(next_index_to_use);
557    }
558}
559
560#[uniffi::export]
561impl PreparePayResponse {
562    pub fn complete_pay(&self) -> Result<bool, LwkError> {
563        let mut lock = self.inner.lock()?;
564        let response = lock.take().ok_or(LwkError::ObjectConsumed)?;
565        Ok(response.complete_pay()?)
566    }
567
568    pub fn swap_id(&self) -> Result<String, LwkError> {
569        Ok(self
570            .inner
571            .lock()?
572            .as_ref()
573            .ok_or(LwkError::ObjectConsumed)?
574            .swap_id())
575    }
576
577    /// Serialize the prepare pay response data to a json string
578    ///
579    /// This can be used to restore the prepare pay response after a crash
580    pub fn serialize(&self) -> Result<String, LwkError> {
581        Ok(self
582            .inner
583            .lock()?
584            .as_ref()
585            .ok_or(LwkError::ObjectConsumed)?
586            .serialize()?)
587    }
588
589    pub fn uri(&self) -> Result<String, LwkError> {
590        Ok(self
591            .inner
592            .lock()?
593            .as_ref()
594            .ok_or(LwkError::ObjectConsumed)?
595            .uri())
596    }
597
598    pub fn uri_address(&self) -> Result<Arc<Address>, LwkError> {
599        let uri_address = self
600            .inner
601            .lock()?
602            .as_ref()
603            .ok_or(LwkError::ObjectConsumed)?
604            .uri_address()?;
605        Ok(Arc::new(uri_address.into()))
606    }
607    pub fn uri_amount(&self) -> Result<u64, LwkError> {
608        Ok(self
609            .inner
610            .lock()?
611            .as_ref()
612            .ok_or(LwkError::ObjectConsumed)?
613            .uri_amount())
614    }
615
616    /// The fee of the swap provider and the network fee
617    ///
618    /// It is equal to the amount requested onchain minus the amount of the bolt11 invoice
619    pub fn fee(&self) -> Result<Option<u64>, LwkError> {
620        Ok(self
621            .inner
622            .lock()?
623            .as_ref()
624            .ok_or(LwkError::ObjectConsumed)?
625            .fee())
626    }
627
628    /// The fee of the swap provider
629    ///
630    /// It is equal to the invoice amount multiplied by the boltz fee rate.
631    /// For example for paying an invoice of 1000 satoshi with a 0.1% rate would be 1 satoshi.
632    pub fn boltz_fee(&self) -> Result<Option<u64>, LwkError> {
633        Ok(self
634            .inner
635            .lock()?
636            .as_ref()
637            .ok_or(LwkError::ObjectConsumed)?
638            .boltz_fee())
639    }
640
641    pub fn advance(&self) -> Result<PaymentState, LwkError> {
642        let mut lock = self.inner.lock()?;
643        let mut response = lock.take().ok_or(LwkError::ObjectConsumed)?;
644        Ok(match response.advance() {
645            Ok(ControlFlow::Continue(_update)) => {
646                *lock = Some(response);
647                PaymentState::Continue
648            }
649            Ok(ControlFlow::Break(update)) => {
650                *lock = Some(response);
651                if update {
652                    PaymentState::Success
653                } else {
654                    PaymentState::Failed
655                }
656            }
657            Err(lwk_boltz::Error::NoBoltzUpdate) => {
658                *lock = Some(response);
659                return Err(LwkError::NoBoltzUpdate);
660            }
661            Err(e) => return Err(e.into()),
662        })
663    }
664}
665
666#[uniffi::export]
667impl InvoiceResponse {
668    pub fn bolt11_invoice(&self) -> Result<Bolt11Invoice, LwkError> {
669        let bolt11_invoice = self
670            .inner
671            .lock()?
672            .as_ref()
673            .ok_or(LwkError::ObjectConsumed)?
674            .bolt11_invoice();
675        Ok(Bolt11Invoice::from(bolt11_invoice))
676    }
677
678    pub fn swap_id(&self) -> Result<String, LwkError> {
679        Ok(self
680            .inner
681            .lock()?
682            .as_ref()
683            .ok_or(LwkError::ObjectConsumed)?
684            .swap_id())
685    }
686
687    /// The fee of the swap provider and the network fee
688    ///
689    /// It is equal to the amount of the invoice minus the amount of the onchain transaction.
690    pub fn fee(&self) -> Result<Option<u64>, LwkError> {
691        Ok(self
692            .inner
693            .lock()?
694            .as_ref()
695            .ok_or(LwkError::ObjectConsumed)?
696            .fee())
697    }
698
699    /// The fee of the swap provider
700    ///
701    /// It is equal to the invoice amount multiplied by the boltz fee rate.
702    /// For example for receiving an invoice of 10000 satoshi with a 0.25% rate would be 25 satoshi.
703    pub fn boltz_fee(&self) -> Result<Option<u64>, LwkError> {
704        Ok(self
705            .inner
706            .lock()?
707            .as_ref()
708            .ok_or(LwkError::ObjectConsumed)?
709            .boltz_fee())
710    }
711
712    /// The txid of the claim transaction of the swap
713    pub fn claim_txid(&self) -> Result<Option<String>, LwkError> {
714        Ok(self
715            .inner
716            .lock()?
717            .as_ref()
718            .ok_or(LwkError::ObjectConsumed)?
719            .claim_txid()
720            .map(|txid| txid.to_string()))
721    }
722
723    /// Serialize the prepare pay response data to a json string
724    ///
725    /// This can be used to restore the prepare pay response after a crash
726    pub fn serialize(&self) -> Result<String, LwkError> {
727        Ok(self
728            .inner
729            .lock()?
730            .as_ref()
731            .ok_or(LwkError::ObjectConsumed)?
732            .serialize()?)
733    }
734
735    pub fn complete_pay(&self) -> Result<bool, LwkError> {
736        let mut lock = self.inner.lock()?;
737        let response = lock.take().ok_or(LwkError::ObjectConsumed)?;
738        Ok(response.complete_pay()?)
739    }
740
741    pub fn advance(&self) -> Result<PaymentState, LwkError> {
742        let mut lock = self.inner.lock()?;
743        let mut response = lock.take().ok_or(LwkError::ObjectConsumed)?;
744        Ok(match response.advance() {
745            Ok(ControlFlow::Continue(_update)) => {
746                *lock = Some(response);
747                PaymentState::Continue
748            }
749            Ok(ControlFlow::Break(update)) => {
750                *lock = Some(response);
751                if update {
752                    PaymentState::Success
753                } else {
754                    PaymentState::Failed
755                }
756            }
757            Err(lwk_boltz::Error::NoBoltzUpdate) => {
758                *lock = Some(response);
759                return Err(LwkError::NoBoltzUpdate);
760            }
761            Err(e) => return Err(e.into()),
762        })
763    }
764}
765
766#[uniffi::export]
767impl LockupResponse {
768    pub fn swap_id(&self) -> Result<String, LwkError> {
769        Ok(self
770            .inner
771            .lock()?
772            .as_ref()
773            .ok_or(LwkError::ObjectConsumed)?
774            .swap_id())
775    }
776
777    pub fn lockup_address(&self) -> Result<String, LwkError> {
778        Ok(self
779            .inner
780            .lock()?
781            .as_ref()
782            .ok_or(LwkError::ObjectConsumed)?
783            .lockup_address()
784            .to_string())
785    }
786
787    pub fn expected_amount(&self) -> Result<u64, LwkError> {
788        Ok(self
789            .inner
790            .lock()?
791            .as_ref()
792            .ok_or(LwkError::ObjectConsumed)?
793            .expected_amount())
794    }
795
796    pub fn chain_from(&self) -> Result<String, LwkError> {
797        Ok(self
798            .inner
799            .lock()?
800            .as_ref()
801            .ok_or(LwkError::ObjectConsumed)?
802            .chain_from()
803            .to_string())
804    }
805
806    pub fn chain_to(&self) -> Result<String, LwkError> {
807        Ok(self
808            .inner
809            .lock()?
810            .as_ref()
811            .ok_or(LwkError::ObjectConsumed)?
812            .chain_to()
813            .to_string())
814    }
815
816    /// The fee of the swap provider and the network fee
817    ///
818    /// It is equal to the amount requested minus the amount sent to the claim address.
819    pub fn fee(&self) -> Result<Option<u64>, LwkError> {
820        Ok(self
821            .inner
822            .lock()?
823            .as_ref()
824            .ok_or(LwkError::ObjectConsumed)?
825            .fee())
826    }
827
828    /// The fee of the swap provider
829    ///
830    /// It is equal to the swap amount multiplied by the boltz fee rate.
831    pub fn boltz_fee(&self) -> Result<Option<u64>, LwkError> {
832        Ok(self
833            .inner
834            .lock()?
835            .as_ref()
836            .ok_or(LwkError::ObjectConsumed)?
837            .boltz_fee())
838    }
839
840    pub fn advance(&self) -> Result<PaymentState, LwkError> {
841        let mut lock = self.inner.lock()?;
842        let mut response = lock.take().ok_or(LwkError::ObjectConsumed)?;
843        Ok(match response.advance() {
844            Ok(ControlFlow::Continue(_update)) => {
845                *lock = Some(response);
846                PaymentState::Continue
847            }
848            Ok(ControlFlow::Break(update)) => {
849                if update {
850                    PaymentState::Success
851                } else {
852                    PaymentState::Failed
853                }
854            }
855            Err(lwk_boltz::Error::NoBoltzUpdate) => {
856                *lock = Some(response);
857                return Err(LwkError::NoBoltzUpdate);
858            }
859            Err(e) => return Err(e.into()),
860        })
861    }
862
863    pub fn complete(&self) -> Result<bool, LwkError> {
864        let mut lock = self.inner.lock()?;
865        let response = lock.take().ok_or(LwkError::ObjectConsumed)?;
866        Ok(response.complete()?)
867    }
868
869    pub fn serialize(&self) -> Result<String, LwkError> {
870        Ok(self
871            .inner
872            .lock()?
873            .as_ref()
874            .ok_or(LwkError::ObjectConsumed)?
875            .serialize()?)
876    }
877}
878
879#[uniffi::export]
880impl WebHook {
881    #[uniffi::constructor]
882    pub fn new(url: String, status: Vec<String>) -> Arc<Self> {
883        Arc::new(Self { url, status })
884    }
885}