Skip to main content

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            boltz_client::error::Error::HTTPStatusNotSuccess(status, body) => {
66                SdkError::generic(format!("Boltz API returned error status {status}: {body}"))
67            }
68            _ => SdkError::generic(format!("{err:?}")),
69        }
70    }
71}
72
73impl From<secp256k1::Error> for SdkError {
74    fn from(err: secp256k1::Error) -> Self {
75        SdkError::generic(format!("{err:?}"))
76    }
77}
78
79#[derive(thiserror::Error, Debug)]
80pub enum PaymentError {
81    #[error("The specified funds have already been claimed")]
82    AlreadyClaimed,
83
84    #[error("The specified funds have already been sent")]
85    AlreadyPaid,
86
87    #[error("The payment is already in progress")]
88    PaymentInProgress,
89
90    #[error("Amount must be between {min} and {max}")]
91    AmountOutOfRange { min: u64, max: u64 },
92
93    #[error("Amount is missing: {err}")]
94    AmountMissing { err: String },
95
96    #[error("Asset error: {err}")]
97    AssetError { err: String },
98
99    #[error("Invalid network: {err}")]
100    InvalidNetwork { err: String },
101
102    #[error("Generic error: {err}")]
103    Generic { err: String },
104
105    #[error("The provided fees have expired")]
106    InvalidOrExpiredFees,
107
108    #[error("Cannot pay: not enough funds")]
109    InsufficientFunds,
110
111    #[error("Invalid description: {err}")]
112    InvalidDescription { err: String },
113
114    #[error("The specified invoice is not valid: {err}")]
115    InvalidInvoice { err: String },
116
117    #[error("The generated preimage is not valid")]
118    InvalidPreimage,
119
120    #[error("Boltz did not return any pairs from the request")]
121    PairsNotFound,
122
123    #[error("Payment start could not be verified within the configured timeout")]
124    PaymentTimeout,
125
126    #[error("Could not store the swap details locally")]
127    PersistError,
128
129    #[error("Could not process the Receive Payment: {err}")]
130    ReceiveError { err: String },
131
132    #[error("The payment has been refunded. Reason for failure: {err}")]
133    Refunded { err: String, refund_tx_id: String },
134
135    #[error("The payment is a self-transfer, which is not supported")]
136    SelfTransferNotSupported,
137
138    #[error("Could not process the Send Payment: {err}")]
139    SendError { err: String },
140
141    #[error("Could not sign the transaction: {err}")]
142    SignerError { err: String },
143}
144impl PaymentError {
145    pub(crate) fn asset_error<S: AsRef<str>>(err: S) -> Self {
146        Self::AssetError {
147            err: err.as_ref().to_string(),
148        }
149    }
150
151    pub(crate) fn generic<S: AsRef<str>>(err: S) -> Self {
152        Self::Generic {
153            err: err.as_ref().to_string(),
154        }
155    }
156
157    pub(crate) fn invalid_invoice<S: AsRef<str>>(err: S) -> Self {
158        Self::InvalidInvoice {
159            err: err.as_ref().to_string(),
160        }
161    }
162
163    pub(crate) fn invalid_network<S: AsRef<str>>(err: S) -> Self {
164        Self::InvalidNetwork {
165            err: err.as_ref().to_string(),
166        }
167    }
168
169    pub(crate) fn receive_error<S: AsRef<str>>(err: S) -> Self {
170        Self::ReceiveError {
171            err: err.as_ref().to_string(),
172        }
173    }
174
175    pub(crate) fn amount_missing<S: AsRef<str>>(err: S) -> Self {
176        Self::AmountMissing {
177            err: err.as_ref().to_string(),
178        }
179    }
180}
181
182impl From<Bolt12SemanticError> for PaymentError {
183    fn from(err: Bolt12SemanticError) -> Self {
184        PaymentError::Generic {
185            err: format!("Failed to create BOLT12 invoice: {err:?}"),
186        }
187    }
188}
189
190impl From<boltz_client::error::Error> for PaymentError {
191    fn from(err: boltz_client::error::Error) -> Self {
192        match err {
193            boltz_client::error::Error::HTTP(e) => PaymentError::Generic {
194                err: format!("Could not contact servers: {e:?}"),
195            },
196            boltz_client::error::Error::HTTPStatusNotSuccess(status, body) => {
197                PaymentError::Generic {
198                    err: format!("Boltz API returned error status {status}: {body}"),
199                }
200            }
201            _ => PaymentError::Generic {
202                err: format!("{err:?}"),
203            },
204        }
205    }
206}
207
208impl From<boltz_client::bitcoin::hex::HexToArrayError> for PaymentError {
209    fn from(err: boltz_client::bitcoin::hex::HexToArrayError) -> Self {
210        PaymentError::Generic {
211            err: format!("{err:?}"),
212        }
213    }
214}
215
216impl From<lwk_wollet::Error> for PaymentError {
217    fn from(err: lwk_wollet::Error) -> Self {
218        match err {
219            lwk_wollet::Error::InsufficientFunds { .. } => PaymentError::InsufficientFunds,
220            lwk_wollet::Error::TooManyInputs(count) => PaymentError::Generic {
221                err: format!(
222                    "Transaction would require {count} inputs, which exceeds the maximum of 256 \
223                     supported by the Liquid surjection proof. Consolidate UTXOs and try again."
224                ),
225            },
226            _ => PaymentError::Generic {
227                err: format!("{err:?}"),
228            },
229        }
230    }
231}
232
233#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
234impl From<lwk_wollet::UrlError> for PaymentError {
235    fn from(err: lwk_wollet::UrlError) -> Self {
236        PaymentError::Generic {
237            err: format!("{err:?}"),
238        }
239    }
240}
241
242impl From<lwk_signer::SignerError> for PaymentError {
243    fn from(err: lwk_signer::SignerError) -> Self {
244        PaymentError::SignerError {
245            err: format!("{err:?}"),
246        }
247    }
248}
249
250impl From<anyhow::Error> for PaymentError {
251    fn from(err: anyhow::Error) -> Self {
252        Self::Generic {
253            err: err.to_string(),
254        }
255    }
256}
257
258impl From<PayjoinError> for PaymentError {
259    fn from(err: PayjoinError) -> Self {
260        match err {
261            PayjoinError::InsufficientFunds => PaymentError::InsufficientFunds,
262            _ => PaymentError::Generic {
263                err: format!("{err:?}"),
264            },
265        }
266    }
267}
268
269impl From<rusqlite::Error> for PaymentError {
270    fn from(err: rusqlite::Error) -> Self {
271        log::error!("Persister returned error: {err:?}");
272        Self::PersistError
273    }
274}
275
276impl From<SdkError> for PaymentError {
277    fn from(err: SdkError) -> Self {
278        Self::Generic {
279            err: err.to_string(),
280        }
281    }
282}
283
284impl From<sdk_common::bitcoin::util::bip32::Error> for PaymentError {
285    fn from(err: sdk_common::bitcoin::util::bip32::Error) -> Self {
286        Self::SignerError {
287            err: err.to_string(),
288        }
289    }
290}
291
292impl From<secp256k1::Error> for PaymentError {
293    fn from(err: secp256k1::Error) -> Self {
294        Self::Generic {
295            err: err.to_string(),
296        }
297    }
298}
299
300impl From<PaymentError> for LnUrlAuthError {
301    fn from(err: PaymentError) -> Self {
302        Self::Generic {
303            err: err.to_string(),
304        }
305    }
306}
307
308impl From<PaymentError> for LnUrlPayError {
309    fn from(err: PaymentError) -> Self {
310        match err {
311            PaymentError::AlreadyPaid => Self::AlreadyPaid,
312            PaymentError::AmountOutOfRange { min, max } => Self::InvalidAmount {
313                err: format!("Amount must be between {min} and {max}"),
314            },
315            PaymentError::AmountMissing { err } => Self::InvalidAmount {
316                err: format!("Amount is missing: {err}"),
317            },
318            PaymentError::InvalidNetwork { err } => Self::InvalidNetwork { err },
319            PaymentError::InsufficientFunds => Self::InsufficientBalance { err: String::new() },
320            PaymentError::InvalidInvoice { err } => Self::InvalidInvoice { err },
321            PaymentError::PaymentTimeout => Self::PaymentTimeout { err: String::new() },
322            _ => Self::Generic {
323                err: err.to_string(),
324            },
325        }
326    }
327}
328
329impl From<PaymentError> for LnUrlWithdrawError {
330    fn from(err: PaymentError) -> Self {
331        Self::Generic {
332            err: err.to_string(),
333        }
334    }
335}
336
337pub(crate) fn is_txn_mempool_conflict_error(err: &Error) -> bool {
338    err.to_string().contains("txn-mempool-conflict")
339}