breez_sdk_spark/models/
payment_observer.rs

1use std::sync::Arc;
2
3use spark_wallet::{TransferId, TransferObserverError};
4use thiserror::Error;
5
6#[derive(Debug, Clone)]
7#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
8pub struct ProvisionalPayment {
9    /// Unique identifier for the payment
10    pub payment_id: String,
11    /// Amount in satoshis or token base units
12    pub amount: u128,
13    /// Details of the payment
14    pub details: ProvisionalPaymentDetails,
15}
16
17#[derive(Debug, Clone)]
18#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
19pub enum ProvisionalPaymentDetails {
20    Bitcoin {
21        /// Onchain Bitcoin address
22        withdrawal_address: String,
23    },
24    Lightning {
25        /// BOLT11 invoice
26        invoice: String,
27    },
28    Spark {
29        /// Spark pay request being paid (either a Spark address or a Spark invoice)
30        pay_request: String,
31    },
32    Token {
33        /// Token identifier
34        token_id: String,
35        /// Spark pay request being paid (either a Spark address or a Spark invoice)
36        pay_request: String,
37    },
38}
39
40#[derive(Debug, Error, Clone)]
41#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
42pub enum PaymentObserverError {
43    #[error("Service connectivity: {0}")]
44    ServiceConnectivity(String),
45    #[error("Generic: {0}")]
46    Generic(String),
47}
48
49impl From<PaymentObserverError> for TransferObserverError {
50    fn from(error: PaymentObserverError) -> Self {
51        match error {
52            PaymentObserverError::ServiceConnectivity(msg) => {
53                TransferObserverError::ServiceConnectivity(msg)
54            }
55            PaymentObserverError::Generic(msg) => TransferObserverError::Generic(msg),
56        }
57    }
58}
59
60/// This interface is used to observe outgoing payments before Lightning, Spark and onchain Bitcoin payments.
61/// If the implementation returns an error, the payment is cancelled.
62#[cfg_attr(feature = "uniffi", uniffi::export(with_foreign))]
63#[macros::async_trait]
64pub trait PaymentObserver: Send + Sync {
65    /// Called before Lightning, Spark or onchain Bitcoin payments are made
66    async fn before_send(
67        &self,
68        payments: Vec<ProvisionalPayment>,
69    ) -> Result<(), PaymentObserverError>;
70}
71
72pub(crate) struct SparkTransferObserver {
73    inner: Arc<dyn PaymentObserver>,
74}
75
76impl SparkTransferObserver {
77    pub fn new(inner: Arc<dyn PaymentObserver>) -> Self {
78        Self { inner }
79    }
80}
81
82#[macros::async_trait]
83impl spark_wallet::TransferObserver for SparkTransferObserver {
84    async fn before_coop_exit(
85        &self,
86        transfer_id: &TransferId,
87        withdrawal_address: &bitcoin::Address,
88        amount_sats: u64,
89    ) -> Result<(), TransferObserverError> {
90        Ok(self
91            .inner
92            .before_send(vec![ProvisionalPayment {
93                payment_id: transfer_id.to_string(),
94                amount: u128::from(amount_sats),
95                details: ProvisionalPaymentDetails::Bitcoin {
96                    withdrawal_address: withdrawal_address.to_string(),
97                },
98            }])
99            .await?)
100    }
101    async fn before_send_lightning_payment(
102        &self,
103        transfer_id: &TransferId,
104        invoice: &str,
105        amount_sats: u64,
106    ) -> Result<(), TransferObserverError> {
107        Ok(self
108            .inner
109            .before_send(vec![ProvisionalPayment {
110                payment_id: transfer_id.to_string(),
111                amount: u128::from(amount_sats),
112                details: ProvisionalPaymentDetails::Lightning {
113                    invoice: invoice.to_string(),
114                },
115            }])
116            .await?)
117    }
118
119    async fn before_send_token(
120        &self,
121        tx_id: &str,
122        token_id: &str,
123        receiver_outputs: Vec<spark_wallet::ReceiverTokenOutput>,
124    ) -> Result<(), TransferObserverError> {
125        Ok(self
126            .inner
127            .before_send(
128                receiver_outputs
129                    .into_iter()
130                    .enumerate()
131                    .map(|(index, output)| ProvisionalPayment {
132                        payment_id: format!("{tx_id}:{index}"),
133                        amount: output.amount,
134                        details: ProvisionalPaymentDetails::Token {
135                            token_id: token_id.to_string(),
136                            pay_request: output.pay_request,
137                        },
138                    })
139                    .collect(),
140            )
141            .await?)
142    }
143
144    async fn before_send_transfer(
145        &self,
146        transfer_id: &TransferId,
147        receiver_address: &str,
148        amount_sats: u64,
149    ) -> Result<(), TransferObserverError> {
150        Ok(self
151            .inner
152            .before_send(vec![ProvisionalPayment {
153                payment_id: transfer_id.to_string(),
154                amount: u128::from(amount_sats),
155                details: ProvisionalPaymentDetails::Spark {
156                    pay_request: receiver_address.to_string(),
157                },
158            }])
159            .await?)
160    }
161}