breez_sdk_liquid/
error.rs

1use anyhow::Error;
2use lwk_wollet::secp256k1;
3use sdk_common::{
4    lightning_with_bolt12::offers::parse::Bolt12SemanticError,
5    prelude::{LnUrlAuthError, LnUrlPayError, LnUrlWithdrawError},
6};
7
8use crate::payjoin::error::PayjoinError;
9
10pub type SdkResult<T, E = SdkError> = Result<T, E>;
11
12#[macro_export]
13macro_rules! ensure_sdk {
14    ($cond:expr, $err:expr) => {
15        if !$cond {
16            return Err($err);
17        }
18    };
19}
20
21// TODO Unify error enum
22#[derive(Debug, thiserror::Error)]
23pub enum SdkError {
24    #[error("Liquid SDK instance is already running")]
25    AlreadyStarted,
26
27    #[error("Error: {err}")]
28    Generic { err: String },
29
30    #[error("Network {network} is not currently supported")]
31    NetworkNotSupported { network: String },
32
33    #[error("Liquid SDK instance is not running")]
34    NotStarted,
35
36    #[error("Service connectivity: {err}")]
37    ServiceConnectivity { err: String },
38}
39impl SdkError {
40    pub fn generic<T: AsRef<str>>(err: T) -> Self {
41        Self::Generic {
42            err: err.as_ref().to_string(),
43        }
44    }
45
46    pub(crate) fn network_not_supported<T: ToString>(network: T) -> Self {
47        Self::NetworkNotSupported {
48            network: network.to_string(),
49        }
50    }
51}
52
53impl From<anyhow::Error> for SdkError {
54    fn from(e: Error) -> Self {
55        SdkError::generic(e.to_string())
56    }
57}
58
59impl From<boltz_client::error::Error> for SdkError {
60    fn from(err: boltz_client::error::Error) -> Self {
61        match err {
62            boltz_client::error::Error::HTTP(e) => {
63                SdkError::generic(format!("Could not contact servers: {e:?}"))
64            }
65            _ => SdkError::generic(format!("{err:?}")),
66        }
67    }
68}
69
70impl From<secp256k1::Error> for SdkError {
71    fn from(err: secp256k1::Error) -> Self {
72        SdkError::generic(format!("{err:?}"))
73    }
74}
75
76#[derive(thiserror::Error, Debug)]
77pub enum PaymentError {
78    #[error("The specified funds have already been claimed")]
79    AlreadyClaimed,
80
81    #[error("The specified funds have already been sent")]
82    AlreadyPaid,
83
84    #[error("The payment is already in progress")]
85    PaymentInProgress,
86
87    #[error("Amount must be between {min} and {max}")]
88    AmountOutOfRange { min: u64, max: u64 },
89
90    #[error("Amount is missing: {err}")]
91    AmountMissing { err: String },
92
93    #[error("Asset error: {err}")]
94    AssetError { err: String },
95
96    #[error("Invalid network: {err}")]
97    InvalidNetwork { err: String },
98
99    #[error("Generic error: {err}")]
100    Generic { err: String },
101
102    #[error("The provided fees have expired")]
103    InvalidOrExpiredFees,
104
105    #[error("Cannot pay: not enough funds")]
106    InsufficientFunds,
107
108    #[error("Invalid description: {err}")]
109    InvalidDescription { err: String },
110
111    #[error("The specified invoice is not valid: {err}")]
112    InvalidInvoice { err: String },
113
114    #[error("The generated preimage is not valid")]
115    InvalidPreimage,
116
117    #[error("Boltz did not return any pairs from the request")]
118    PairsNotFound,
119
120    #[error("Payment start could not be verified within the configured timeout")]
121    PaymentTimeout,
122
123    #[error("Could not store the swap details locally")]
124    PersistError,
125
126    #[error("Could not process the Receive Payment: {err}")]
127    ReceiveError { err: String },
128
129    #[error("The payment has been refunded. Reason for failure: {err}")]
130    Refunded { err: String, refund_tx_id: String },
131
132    #[error("The payment is a self-transfer, which is not supported")]
133    SelfTransferNotSupported,
134
135    #[error("Could not process the Send Payment: {err}")]
136    SendError { err: String },
137
138    #[error("Could not sign the transaction: {err}")]
139    SignerError { err: String },
140}
141impl PaymentError {
142    pub(crate) fn asset_error<S: AsRef<str>>(err: S) -> Self {
143        Self::AssetError {
144            err: err.as_ref().to_string(),
145        }
146    }
147
148    pub(crate) fn generic<S: AsRef<str>>(err: S) -> Self {
149        Self::Generic {
150            err: err.as_ref().to_string(),
151        }
152    }
153
154    pub(crate) fn invalid_invoice<S: AsRef<str>>(err: S) -> Self {
155        Self::InvalidInvoice {
156            err: err.as_ref().to_string(),
157        }
158    }
159
160    pub(crate) fn invalid_network<S: AsRef<str>>(err: S) -> Self {
161        Self::InvalidNetwork {
162            err: err.as_ref().to_string(),
163        }
164    }
165
166    pub(crate) fn receive_error<S: AsRef<str>>(err: S) -> Self {
167        Self::ReceiveError {
168            err: err.as_ref().to_string(),
169        }
170    }
171
172    pub(crate) fn amount_missing<S: AsRef<str>>(err: S) -> Self {
173        Self::AmountMissing {
174            err: err.as_ref().to_string(),
175        }
176    }
177}
178
179impl From<Bolt12SemanticError> for PaymentError {
180    fn from(err: Bolt12SemanticError) -> Self {
181        PaymentError::Generic {
182            err: format!("Failed to create BOLT12 invoice: {err:?}"),
183        }
184    }
185}
186
187impl From<boltz_client::error::Error> for PaymentError {
188    fn from(err: boltz_client::error::Error) -> Self {
189        match err {
190            boltz_client::error::Error::HTTP(e) => PaymentError::Generic {
191                err: format!("Could not contact servers: {e:?}"),
192            },
193            _ => PaymentError::Generic {
194                err: format!("{err:?}"),
195            },
196        }
197    }
198}
199
200impl From<boltz_client::bitcoin::hex::HexToArrayError> for PaymentError {
201    fn from(err: boltz_client::bitcoin::hex::HexToArrayError) -> Self {
202        PaymentError::Generic {
203            err: format!("{err:?}"),
204        }
205    }
206}
207
208impl From<lwk_wollet::Error> for PaymentError {
209    fn from(err: lwk_wollet::Error) -> Self {
210        match err {
211            lwk_wollet::Error::InsufficientFunds { .. } => PaymentError::InsufficientFunds,
212            _ => PaymentError::Generic {
213                err: format!("{err:?}"),
214            },
215        }
216    }
217}
218
219#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
220impl From<lwk_wollet::UrlError> for PaymentError {
221    fn from(err: lwk_wollet::UrlError) -> Self {
222        PaymentError::Generic {
223            err: format!("{err:?}"),
224        }
225    }
226}
227
228impl From<lwk_signer::SignerError> for PaymentError {
229    fn from(err: lwk_signer::SignerError) -> Self {
230        PaymentError::SignerError {
231            err: format!("{err:?}"),
232        }
233    }
234}
235
236impl From<anyhow::Error> for PaymentError {
237    fn from(err: anyhow::Error) -> Self {
238        Self::Generic {
239            err: err.to_string(),
240        }
241    }
242}
243
244impl From<PayjoinError> for PaymentError {
245    fn from(err: PayjoinError) -> Self {
246        match err {
247            PayjoinError::InsufficientFunds => PaymentError::InsufficientFunds,
248            _ => PaymentError::Generic {
249                err: format!("{err:?}"),
250            },
251        }
252    }
253}
254
255impl From<rusqlite::Error> for PaymentError {
256    fn from(err: rusqlite::Error) -> Self {
257        log::error!("Persister returned error: {err:?}");
258        Self::PersistError
259    }
260}
261
262impl From<SdkError> for PaymentError {
263    fn from(err: SdkError) -> Self {
264        Self::Generic {
265            err: err.to_string(),
266        }
267    }
268}
269
270impl From<sdk_common::bitcoin::util::bip32::Error> for PaymentError {
271    fn from(err: sdk_common::bitcoin::util::bip32::Error) -> Self {
272        Self::SignerError {
273            err: err.to_string(),
274        }
275    }
276}
277
278impl From<secp256k1::Error> for PaymentError {
279    fn from(err: secp256k1::Error) -> Self {
280        Self::Generic {
281            err: err.to_string(),
282        }
283    }
284}
285
286impl From<PaymentError> for LnUrlAuthError {
287    fn from(err: PaymentError) -> Self {
288        Self::Generic {
289            err: err.to_string(),
290        }
291    }
292}
293
294impl From<PaymentError> for LnUrlPayError {
295    fn from(err: PaymentError) -> Self {
296        match err {
297            PaymentError::AlreadyPaid => Self::AlreadyPaid,
298            PaymentError::AmountOutOfRange { min, max } => Self::InvalidAmount {
299                err: format!("Amount must be between {min} and {max}"),
300            },
301            PaymentError::AmountMissing { err } => Self::InvalidAmount {
302                err: format!("Amount is missing: {err}"),
303            },
304            PaymentError::InvalidNetwork { err } => Self::InvalidNetwork { err },
305            PaymentError::InsufficientFunds => Self::InsufficientBalance { err: String::new() },
306            PaymentError::InvalidInvoice { err } => Self::InvalidInvoice { err },
307            PaymentError::PaymentTimeout => Self::PaymentTimeout { err: String::new() },
308            _ => Self::Generic {
309                err: err.to_string(),
310            },
311        }
312    }
313}
314
315impl From<PaymentError> for LnUrlWithdrawError {
316    fn from(err: PaymentError) -> Self {
317        Self::Generic {
318            err: err.to_string(),
319        }
320    }
321}
322
323pub(crate) fn is_txn_mempool_conflict_error(err: &Error) -> bool {
324    err.to_string().contains("txn-mempool-conflict")
325}