Skip to main content

breez_sdk_spark/
error.rs

1use crate::{
2    Fee,
3    lnurl::LnurlServerError,
4    persist::{self},
5};
6use bitcoin::consensus::encode::FromHexError;
7use breez_sdk_common::error::ServiceConnectivityError;
8use platform_utils::time::SystemTimeError;
9use serde::{Deserialize, Serialize};
10use spark_wallet::SparkWalletError;
11use std::{convert::Infallible, num::TryFromIntError};
12use thiserror::Error;
13use tracing_subscriber::util::TryInitError;
14
15/// Error type for the `BreezSdk`
16#[derive(Debug, Error, Clone)]
17#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
18pub enum SdkError {
19    #[error("SparkSdkError: {0}")]
20    SparkError(String),
21
22    #[error("Insufficient funds{}", .token_identifier.as_deref().map(|t| format!(" for token {t}")).unwrap_or_default())]
23    InsufficientFunds {
24        /// The token that cannot cover the payment. Unset when the shortfall is
25        /// in sats or when no single token can be named.
26        token_identifier: Option<String>,
27    },
28
29    #[error("Invalid UUID: {0}")]
30    InvalidUuid(String),
31
32    /// Invalid input error
33    #[error("Invalid input: {0}")]
34    InvalidInput(String),
35
36    /// Network error
37    #[error("Network error: {0}")]
38    NetworkError(String),
39
40    /// Storage error
41    #[error("Storage error: {0}")]
42    StorageError(String),
43
44    #[error("Chain service error: {0}")]
45    ChainServiceError(String),
46
47    #[error(
48        "Max deposit claim fee exceeded for utxo: {tx}:{vout} with max fee: {max_fee:?} and required fee: {required_fee_sats} sats or {required_fee_rate_sat_per_vbyte} sats/vbyte"
49    )]
50    MaxDepositClaimFeeExceeded {
51        tx: String,
52        vout: u32,
53        max_fee: Option<Fee>,
54        required_fee_sats: u64,
55        required_fee_rate_sat_per_vbyte: u64,
56    },
57
58    #[error("Missing utxo: {tx}:{vout}")]
59    MissingUtxo { tx: String, vout: u32 },
60
61    /// Another claim on this deposit is already running.
62    #[error("Deposit claim already in progress: {tx}:{vout}")]
63    DepositClaimInProgress { tx: String, vout: u32 },
64
65    /// A refund for this deposit is already on the network, and the requested
66    /// replacement does not pay enough to displace it.
67    #[error(
68        "A refund is already pending at {pending_fee_sats} sats: a replacement must pay at least {required_fee_sats} sats"
69    )]
70    RefundReplacementFeeTooLow {
71        pending_fee_sats: u64,
72        required_fee_sats: u64,
73    },
74
75    #[error("Lnurl error: {0}")]
76    LnurlError(String),
77
78    #[error("Signer error: {0}")]
79    Signer(String),
80
81    /// `optimize_leaves` was called while another optimization run (auto or
82    /// manual) was already in flight.
83    #[error("Optimization is already in progress")]
84    OptimizationAlreadyRunning,
85
86    /// `optimize_leaves` was preempted by the SDK to free leaves for a
87    /// higher-priority operation (typically a payment).
88    #[error("Optimization was cancelled by the SDK to free leaves")]
89    OptimizationCancelled,
90
91    /// The provided CPFP funding is too low to cover the exit's on-chain fees.
92    #[error("Insufficient CPFP funding: need at least {required_sat} sats")]
93    InsufficientCpfpFunds { required_sat: u64 },
94
95    #[error("Error: {0}")]
96    Generic(String),
97}
98
99impl From<crate::chain::ChainServiceError> for SdkError {
100    fn from(e: crate::chain::ChainServiceError) -> Self {
101        SdkError::ChainServiceError(e.to_string())
102    }
103}
104
105impl From<breez_sdk_common::lnurl::error::LnurlError> for SdkError {
106    fn from(e: breez_sdk_common::lnurl::error::LnurlError) -> Self {
107        SdkError::LnurlError(e.to_string())
108    }
109}
110
111impl From<breez_sdk_common::input::ParseError> for SdkError {
112    fn from(e: breez_sdk_common::input::ParseError) -> Self {
113        SdkError::InvalidInput(e.to_string())
114    }
115}
116
117impl From<bitcoin::address::ParseError> for SdkError {
118    fn from(e: bitcoin::address::ParseError) -> Self {
119        SdkError::InvalidInput(e.to_string())
120    }
121}
122
123impl From<flashnet::FlashnetError> for SdkError {
124    fn from(e: flashnet::FlashnetError) -> Self {
125        match e {
126            flashnet::FlashnetError::Network { reason, code } => {
127                let code = match code {
128                    Some(c) => format!(" (code: {c})"),
129                    None => String::new(),
130                };
131                SdkError::NetworkError(format!("{reason}{code}"))
132            }
133            _ => SdkError::Generic(e.to_string()),
134        }
135    }
136}
137
138impl From<boltz_client::BoltzError> for SdkError {
139    fn from(e: boltz_client::BoltzError) -> Self {
140        use boltz_client::BoltzError;
141        match e {
142            BoltzError::Api { reason, code } => {
143                let code = match code {
144                    Some(c) => format!(" (code: {c})"),
145                    None => String::new(),
146                };
147                SdkError::NetworkError(format!("Boltz API: {reason}{code}"))
148            }
149            BoltzError::WebSocket(s) => SdkError::NetworkError(format!("Boltz WebSocket: {s}")),
150            BoltzError::Store(s) => SdkError::StorageError(format!("Boltz store: {s}")),
151            BoltzError::AmountOutOfRange { .. }
152            | BoltzError::QuoteExpired
153            | BoltzError::InvalidQuote(_)
154            | BoltzError::QuoteDegradedBeyondSlippage { .. }
155            | BoltzError::DuplicatePreimage
156            | BoltzError::InvalidConfig(_) => SdkError::InvalidInput(e.to_string()),
157            _ => SdkError::Generic(format!("Boltz: {e}")),
158        }
159    }
160}
161
162impl From<crate::token_conversion::ConversionError> for SdkError {
163    fn from(e: crate::token_conversion::ConversionError) -> Self {
164        use crate::token_conversion::ConversionError;
165        match e {
166            ConversionError::NoPoolsAvailable => {
167                SdkError::Generic("No conversion pools available".to_string())
168            }
169            ConversionError::ConversionFailed(msg)
170            | ConversionError::ValidationFailed(msg)
171            | ConversionError::RefundFailed(msg) => SdkError::Generic(msg),
172            ConversionError::DuplicateTransfer => {
173                SdkError::Generic("Duplicate transfer: conversion already handled".to_string())
174            }
175            ConversionError::Sdk(e) => e,
176            ConversionError::Storage(e) => SdkError::StorageError(e.to_string()),
177            ConversionError::Wallet(e) => SdkError::SparkError(e.to_string()),
178        }
179    }
180}
181
182impl From<persist::StorageError> for SdkError {
183    fn from(e: persist::StorageError) -> Self {
184        match e {
185            persist::StorageError::NotFound => SdkError::InvalidInput("Not found".to_string()),
186            _ => SdkError::StorageError(e.to_string()),
187        }
188    }
189}
190
191impl From<Infallible> for SdkError {
192    fn from(value: Infallible) -> Self {
193        SdkError::Generic(value.to_string())
194    }
195}
196
197impl From<String> for SdkError {
198    fn from(s: String) -> Self {
199        Self::Generic(s)
200    }
201}
202
203impl From<&str> for SdkError {
204    fn from(s: &str) -> Self {
205        Self::Generic(s.to_string())
206    }
207}
208
209impl From<SystemTimeError> for SdkError {
210    fn from(e: SystemTimeError) -> Self {
211        SdkError::Generic(e.to_string())
212    }
213}
214
215impl From<TryFromIntError> for SdkError {
216    fn from(e: TryFromIntError) -> Self {
217        SdkError::Generic(e.to_string())
218    }
219}
220
221impl From<serde_json::Error> for SdkError {
222    fn from(e: serde_json::Error) -> Self {
223        SdkError::Generic(e.to_string())
224    }
225}
226
227impl From<SparkWalletError> for SdkError {
228    fn from(e: SparkWalletError) -> Self {
229        match e {
230            SparkWalletError::InsufficientFunds => SdkError::InsufficientFunds {
231                token_identifier: None,
232            },
233            SparkWalletError::TokenOutputServiceError(
234                spark_wallet::TokenOutputServiceError::InsufficientFunds { token_identifier },
235            ) => SdkError::InsufficientFunds { token_identifier },
236            SparkWalletError::ServiceError(spark_wallet::ServiceError::InvalidInput(msg)) => {
237                SdkError::InvalidInput(msg)
238            }
239            SparkWalletError::ServiceError(
240                spark_wallet::ServiceError::InsufficientCpfpBudget { required_sat },
241            ) => SdkError::InsufficientCpfpFunds { required_sat },
242            _ => SdkError::SparkError(e.to_string()),
243        }
244    }
245}
246
247impl From<spark_wallet::OptimizationError> for SdkError {
248    fn from(e: spark_wallet::OptimizationError) -> Self {
249        match e {
250            spark_wallet::OptimizationError::AlreadyRunning => SdkError::OptimizationAlreadyRunning,
251            spark_wallet::OptimizationError::Cancelled => SdkError::OptimizationCancelled,
252            spark_wallet::OptimizationError::Tree(inner) => SdkError::SparkError(inner.to_string()),
253        }
254    }
255}
256
257impl From<FromHexError> for SdkError {
258    fn from(e: FromHexError) -> Self {
259        SdkError::Generic(e.to_string())
260    }
261}
262
263impl From<uuid::Error> for SdkError {
264    fn from(e: uuid::Error) -> Self {
265        SdkError::InvalidUuid(e.to_string())
266    }
267}
268
269impl From<ServiceConnectivityError> for SdkError {
270    fn from(value: ServiceConnectivityError) -> Self {
271        SdkError::NetworkError(value.to_string())
272    }
273}
274
275impl From<LnurlServerError> for SdkError {
276    fn from(value: LnurlServerError) -> Self {
277        match value {
278            LnurlServerError::InvalidApiKey => {
279                SdkError::InvalidInput("Invalid api key".to_string())
280            }
281            LnurlServerError::Network {
282                statuscode,
283                message,
284            } => SdkError::NetworkError(format!(
285                "network request failed with status {statuscode}: {}",
286                message.unwrap_or(String::new())
287            )),
288            LnurlServerError::RequestFailure(e) => SdkError::NetworkError(e),
289            LnurlServerError::SigningError(e) => {
290                SdkError::Generic(format!("Failed to sign message: {e}"))
291            }
292        }
293    }
294}
295
296impl From<TryInitError> for SdkError {
297    fn from(_value: TryInitError) -> Self {
298        SdkError::Generic("Logging can only be initialized once".to_string())
299    }
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize, Error, PartialEq)]
303#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
304pub enum DepositClaimError {
305    #[error(
306        "Max deposit claim fee exceeded for utxo: {tx}:{vout} with max fee: {max_fee:?} and required fee: {required_fee_sats} sats or {required_fee_rate_sat_per_vbyte} sats/vbyte"
307    )]
308    MaxDepositClaimFeeExceeded {
309        tx: String,
310        vout: u32,
311        max_fee: Option<Fee>,
312        required_fee_sats: u64,
313        required_fee_rate_sat_per_vbyte: u64,
314    },
315
316    #[error("Missing utxo: {tx}:{vout}")]
317    MissingUtxo { tx: String, vout: u32 },
318
319    #[error("Generic error: {message}")]
320    Generic { message: String },
321}
322
323impl From<SdkError> for DepositClaimError {
324    fn from(value: SdkError) -> Self {
325        match value {
326            SdkError::MaxDepositClaimFeeExceeded {
327                tx,
328                vout,
329                max_fee,
330                required_fee_sats,
331                required_fee_rate_sat_per_vbyte,
332            } => DepositClaimError::MaxDepositClaimFeeExceeded {
333                tx,
334                vout,
335                max_fee,
336                required_fee_sats,
337                required_fee_rate_sat_per_vbyte,
338            },
339            SdkError::MissingUtxo { tx, vout } => DepositClaimError::MissingUtxo { tx, vout },
340            SdkError::Generic(e) => DepositClaimError::Generic { message: e },
341            _ => DepositClaimError::Generic {
342                message: value.to_string(),
343            },
344        }
345    }
346}
347
348/// Error type for signer operations
349#[derive(Debug, Error, Clone)]
350#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
351pub enum SignerError {
352    #[error("Key derivation error: {0}")]
353    KeyDerivation(String),
354
355    #[error("Signing error: {0}")]
356    Signing(String),
357
358    #[error("Encryption error: {0}")]
359    Encryption(String),
360
361    #[error("Decryption error: {0}")]
362    Decryption(String),
363
364    #[error("Encryption unavailable: {0}")]
365    EncryptionUnavailable(String),
366
367    #[error("FROST error: {0}")]
368    Frost(String),
369
370    #[error("Invalid input: {0}")]
371    InvalidInput(String),
372
373    #[error("Generic signer error: {0}")]
374    Generic(String),
375}
376
377impl From<String> for SignerError {
378    fn from(s: String) -> Self {
379        SignerError::Generic(s)
380    }
381}
382
383impl From<&str> for SignerError {
384    fn from(s: &str) -> Self {
385        SignerError::Generic(s.to_string())
386    }
387}