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