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    /// The provided CPFP funding is too low to cover the exit's on-chain fees.
74    #[error("Insufficient CPFP funding: need at least {required_sat} sats")]
75    InsufficientCpfpFunds { required_sat: u64 },
76
77    /// A provided funding UTXO was already spent on-chain by a transaction that
78    /// is not the expected fan-out, so it cannot fund this exit.
79    #[error("Funding UTXO {txid}:{vout} was spent by an unrelated transaction")]
80    FundingUtxoConflict { txid: String, vout: u32 },
81
82    #[error("Error: {0}")]
83    Generic(String),
84}
85
86impl From<crate::chain::ChainServiceError> for SdkError {
87    fn from(e: crate::chain::ChainServiceError) -> Self {
88        SdkError::ChainServiceError(e.to_string())
89    }
90}
91
92impl From<breez_sdk_common::lnurl::error::LnurlError> for SdkError {
93    fn from(e: breez_sdk_common::lnurl::error::LnurlError) -> Self {
94        SdkError::LnurlError(e.to_string())
95    }
96}
97
98impl From<breez_sdk_common::input::ParseError> for SdkError {
99    fn from(e: breez_sdk_common::input::ParseError) -> Self {
100        SdkError::InvalidInput(e.to_string())
101    }
102}
103
104impl From<bitcoin::address::ParseError> for SdkError {
105    fn from(e: bitcoin::address::ParseError) -> Self {
106        SdkError::InvalidInput(e.to_string())
107    }
108}
109
110impl From<flashnet::FlashnetError> for SdkError {
111    fn from(e: flashnet::FlashnetError) -> Self {
112        match e {
113            flashnet::FlashnetError::Network { reason, code } => {
114                let code = match code {
115                    Some(c) => format!(" (code: {c})"),
116                    None => String::new(),
117                };
118                SdkError::NetworkError(format!("{reason}{code}"))
119            }
120            _ => SdkError::Generic(e.to_string()),
121        }
122    }
123}
124
125impl From<boltz_client::BoltzError> for SdkError {
126    fn from(e: boltz_client::BoltzError) -> Self {
127        use boltz_client::BoltzError;
128        match e {
129            BoltzError::Api { reason, code } => {
130                let code = match code {
131                    Some(c) => format!(" (code: {c})"),
132                    None => String::new(),
133                };
134                SdkError::NetworkError(format!("Boltz API: {reason}{code}"))
135            }
136            BoltzError::WebSocket(s) => SdkError::NetworkError(format!("Boltz WebSocket: {s}")),
137            BoltzError::Store(s) => SdkError::StorageError(format!("Boltz store: {s}")),
138            BoltzError::AmountOutOfRange { .. }
139            | BoltzError::QuoteExpired
140            | BoltzError::InvalidQuote(_)
141            | BoltzError::QuoteDegradedBeyondSlippage { .. }
142            | BoltzError::DuplicatePreimage => SdkError::InvalidInput(e.to_string()),
143            _ => SdkError::Generic(format!("Boltz: {e}")),
144        }
145    }
146}
147
148impl From<crate::token_conversion::ConversionError> for SdkError {
149    fn from(e: crate::token_conversion::ConversionError) -> Self {
150        use crate::token_conversion::ConversionError;
151        match e {
152            ConversionError::NoPoolsAvailable => {
153                SdkError::Generic("No conversion pools available".to_string())
154            }
155            ConversionError::ConversionFailed(msg)
156            | ConversionError::ValidationFailed(msg)
157            | ConversionError::RefundFailed(msg) => SdkError::Generic(msg),
158            ConversionError::DuplicateTransfer => {
159                SdkError::Generic("Duplicate transfer: conversion already handled".to_string())
160            }
161            ConversionError::Sdk(e) => e,
162            ConversionError::Storage(e) => SdkError::StorageError(e.to_string()),
163            ConversionError::Wallet(e) => SdkError::SparkError(e.to_string()),
164        }
165    }
166}
167
168impl From<persist::StorageError> for SdkError {
169    fn from(e: persist::StorageError) -> Self {
170        match e {
171            persist::StorageError::NotFound => SdkError::InvalidInput("Not found".to_string()),
172            _ => SdkError::StorageError(e.to_string()),
173        }
174    }
175}
176
177impl From<Infallible> for SdkError {
178    fn from(value: Infallible) -> Self {
179        SdkError::Generic(value.to_string())
180    }
181}
182
183impl From<String> for SdkError {
184    fn from(s: String) -> Self {
185        Self::Generic(s)
186    }
187}
188
189impl From<&str> for SdkError {
190    fn from(s: &str) -> Self {
191        Self::Generic(s.to_string())
192    }
193}
194
195impl From<SystemTimeError> for SdkError {
196    fn from(e: SystemTimeError) -> Self {
197        SdkError::Generic(e.to_string())
198    }
199}
200
201impl From<TryFromIntError> for SdkError {
202    fn from(e: TryFromIntError) -> Self {
203        SdkError::Generic(e.to_string())
204    }
205}
206
207impl From<serde_json::Error> for SdkError {
208    fn from(e: serde_json::Error) -> Self {
209        SdkError::Generic(e.to_string())
210    }
211}
212
213impl From<SparkWalletError> for SdkError {
214    fn from(e: SparkWalletError) -> Self {
215        match e {
216            SparkWalletError::InsufficientFunds => SdkError::InsufficientFunds,
217            SparkWalletError::ServiceError(spark_wallet::ServiceError::InvalidInput(msg)) => {
218                SdkError::InvalidInput(msg)
219            }
220            SparkWalletError::ServiceError(
221                spark_wallet::ServiceError::InsufficientCpfpBudget { required_sat },
222            ) => SdkError::InsufficientCpfpFunds { required_sat },
223            SparkWalletError::ServiceError(spark_wallet::ServiceError::FundingUtxoConflict {
224                txid,
225                vout,
226            }) => SdkError::FundingUtxoConflict { txid, vout },
227            _ => SdkError::SparkError(e.to_string()),
228        }
229    }
230}
231
232impl From<spark_wallet::OptimizationError> for SdkError {
233    fn from(e: spark_wallet::OptimizationError) -> Self {
234        match e {
235            spark_wallet::OptimizationError::AlreadyRunning => SdkError::OptimizationAlreadyRunning,
236            spark_wallet::OptimizationError::Cancelled => SdkError::OptimizationCancelled,
237            spark_wallet::OptimizationError::Tree(inner) => SdkError::SparkError(inner.to_string()),
238        }
239    }
240}
241
242impl From<FromHexError> for SdkError {
243    fn from(e: FromHexError) -> Self {
244        SdkError::Generic(e.to_string())
245    }
246}
247
248impl From<uuid::Error> for SdkError {
249    fn from(e: uuid::Error) -> Self {
250        SdkError::InvalidUuid(e.to_string())
251    }
252}
253
254impl From<ServiceConnectivityError> for SdkError {
255    fn from(value: ServiceConnectivityError) -> Self {
256        SdkError::NetworkError(value.to_string())
257    }
258}
259
260impl From<LnurlServerError> for SdkError {
261    fn from(value: LnurlServerError) -> Self {
262        match value {
263            LnurlServerError::InvalidApiKey => {
264                SdkError::InvalidInput("Invalid api key".to_string())
265            }
266            LnurlServerError::Network {
267                statuscode,
268                message,
269            } => SdkError::NetworkError(format!(
270                "network request failed with status {statuscode}: {}",
271                message.unwrap_or(String::new())
272            )),
273            LnurlServerError::RequestFailure(e) => SdkError::NetworkError(e),
274            LnurlServerError::SigningError(e) => {
275                SdkError::Generic(format!("Failed to sign message: {e}"))
276            }
277        }
278    }
279}
280
281impl From<TryInitError> for SdkError {
282    fn from(_value: TryInitError) -> Self {
283        SdkError::Generic("Logging can only be initialized once".to_string())
284    }
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, Error, PartialEq)]
288#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
289pub enum DepositClaimError {
290    #[error(
291        "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"
292    )]
293    MaxDepositClaimFeeExceeded {
294        tx: String,
295        vout: u32,
296        max_fee: Option<Fee>,
297        required_fee_sats: u64,
298        required_fee_rate_sat_per_vbyte: u64,
299    },
300
301    #[error("Missing utxo: {tx}:{vout}")]
302    MissingUtxo { tx: String, vout: u32 },
303
304    #[error("Generic error: {message}")]
305    Generic { message: String },
306}
307
308impl From<SdkError> for DepositClaimError {
309    fn from(value: SdkError) -> Self {
310        match value {
311            SdkError::MaxDepositClaimFeeExceeded {
312                tx,
313                vout,
314                max_fee,
315                required_fee_sats,
316                required_fee_rate_sat_per_vbyte,
317            } => DepositClaimError::MaxDepositClaimFeeExceeded {
318                tx,
319                vout,
320                max_fee,
321                required_fee_sats,
322                required_fee_rate_sat_per_vbyte,
323            },
324            SdkError::MissingUtxo { tx, vout } => DepositClaimError::MissingUtxo { tx, vout },
325            SdkError::Generic(e) => DepositClaimError::Generic { message: e },
326            _ => DepositClaimError::Generic {
327                message: value.to_string(),
328            },
329        }
330    }
331}
332
333/// Error type for signer operations
334#[derive(Debug, Error, Clone)]
335#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
336pub enum SignerError {
337    #[error("Key derivation error: {0}")]
338    KeyDerivation(String),
339
340    #[error("Signing error: {0}")]
341    Signing(String),
342
343    #[error("Encryption error: {0}")]
344    Encryption(String),
345
346    #[error("Decryption error: {0}")]
347    Decryption(String),
348
349    #[error("Encryption unavailable: {0}")]
350    EncryptionUnavailable(String),
351
352    #[error("FROST error: {0}")]
353    Frost(String),
354
355    #[error("Invalid input: {0}")]
356    InvalidInput(String),
357
358    #[error("Generic signer error: {0}")]
359    Generic(String),
360}
361
362impl From<String> for SignerError {
363    fn from(s: String) -> Self {
364        SignerError::Generic(s)
365    }
366}
367
368impl From<&str> for SignerError {
369    fn from(s: &str) -> Self {
370        SignerError::Generic(s.to_string())
371    }
372}