spark_rust/
error.rs

1use bitcoin::secp256k1;
2use lightning_invoice::ParseOrSemanticError;
3use std::io;
4use thiserror::Error;
5use tonic::codegen::http::uri::InvalidUri;
6
7// Re-export all errors from subcategories defined below
8pub use self::crypto::CryptoError;
9pub use self::internal::InternalError;
10pub use self::io_error::IoError;
11pub use self::network::NetworkError;
12pub use self::transaction::TransactionError;
13pub use self::validation::ValidationError;
14pub use self::wallet::WalletError;
15
16/// Main error enum that wraps all subcategory errors
17#[derive(Debug, Error)]
18pub enum SparkSdkError {
19    #[error(transparent)]
20    Network(#[from] NetworkError),
21
22    #[error(transparent)]
23    Wallet(#[from] WalletError),
24
25    #[error(transparent)]
26    Crypto(#[from] CryptoError),
27
28    #[error(transparent)]
29    Validation(#[from] ValidationError),
30
31    #[error(transparent)]
32    Transaction(#[from] TransactionError),
33
34    #[error(transparent)]
35    Io(#[from] IoError),
36
37    #[error(transparent)]
38    Internal(#[from] InternalError),
39
40    #[error("General error: {0}")]
41    General(String),
42}
43
44// Network error subcategory
45pub mod network {
46    use super::*;
47
48    #[derive(Debug, Error)]
49    pub enum NetworkError {
50        #[error("Transport error: {0}")]
51        Transport(#[from] tonic::transport::Error),
52
53        #[error("HTTP error: {0}")]
54        Http(#[from] reqwest::Error),
55
56        #[error("Status error: {0}")]
57        Status(#[from] tonic::Status),
58
59        #[error("Invalid URI: {0}")]
60        InvalidUri(#[from] InvalidUri),
61
62        #[error("Invalid URL: {url}")]
63        InvalidUrl {
64            url: String,
65            details: Option<String>,
66        },
67
68        #[error("RPC connection error with endpoint: {uri}")]
69        RpcConnection {
70            uri: String,
71            details: Option<String>,
72        },
73
74        #[error("Bitcoin RPC error: {0}")]
75        BitcoinRpc(String),
76
77        #[error("Invalid GraphQL operation: {0}")]
78        InvalidGraphQLOperation(String),
79
80        #[error("GraphQL error with query: {0}")]
81        GraphQL(String),
82
83        #[error("GraphQL request failed: {status_code}")]
84        GraphQLRequestFailed { status_code: u16 },
85
86        #[error("Faucet request failed with status: {status_code}")]
87        FaucetRequestFailed { status_code: u16 },
88
89        #[error("Invalid response from server")]
90        InvalidResponse,
91    }
92}
93
94// Wallet error subcategory
95pub mod wallet {
96    use super::*;
97
98    #[derive(Debug, Error)]
99    pub enum WalletError {
100        #[error("No leaves found for wallet")]
101        NoLeavesFound,
102
103        #[error("No leaf with exact amount")]
104        NoLeafWithExactAmount,
105
106        #[error("Leaf selection failed: insufficient funds")]
107        LeafSelectionInsufficientFunds,
108
109        #[error("Leaf selection failed: no suitable leaves available")]
110        LeafSelectionNoSuitableLeaves,
111
112        #[error("Leaf not found with id: {0}")]
113        LeafNotFound(String),
114
115        #[error("Leaf is not Bitcoin type: {leaf_id}")]
116        LeafIsNotBitcoin { leaf_id: String },
117
118        #[error("Leaf already exists in wallet: {leaf_id}")]
119        LeafAlreadyExistsInWallet { leaf_id: String },
120
121        #[error("Leaf not available for use: {leaf_id}")]
122        LeafNotAvailableForUse { leaf_id: String },
123
124        #[error("Leaf not using expected lock. Expected: {expected}, Found: {actual}")]
125        LeafNotUsingExpectedLock { expected: String, actual: String },
126
127        #[error("Leaf not found in wallet: {leaf_id}")]
128        LeafNotFoundInWallet { leaf_id: String },
129
130        #[error("Invalid account: {account_id}")]
131        InvalidAccount { account_id: String },
132
133        #[error("Cooperative exit failed: {reason}")]
134        CooperativeExit { reason: String },
135    }
136}
137
138// Crypto error subcategory
139pub mod crypto {
140    use super::*;
141
142    #[derive(Debug, Error)]
143    pub enum CryptoError {
144        #[error("Signature error: {0}")]
145        Secp256k1(#[from] secp256k1::Error),
146
147        #[error("FROST signing failed with job id: {job_id}")]
148        FrostSigning { job_id: String },
149
150        #[error("FROST aggregation failed")]
151        FrostAggregation,
152
153        #[error("Invalid hash: {hash}")]
154        InvalidHash { hash: String },
155
156        #[error("Decryption failed")]
157        Decryption,
158
159        #[error("Secret key not found for public key: {public_key}")]
160        SecretKeyNotFound { public_key: String },
161
162        #[error("Missing keyshare information")]
163        MissingKeyshareInfo,
164
165        #[error("Could not find FROST signature with job id: {job_id}")]
166        FrostSignatureNotFound { job_id: String },
167
168        #[error("Invalid key type: {key_type}")]
169        InvalidKeyType { key_type: String },
170
171        #[error("Invalid seed")]
172        InvalidSeed,
173
174        #[error("Child key derivation failed with path: {derivation_path}")]
175        ChildKeyDerivationError { derivation_path: String },
176
177        #[error("Missing root node signature shares")]
178        MissingRootNodeSignatureShares,
179
180        #[error("Invalid crypto input: {field}")]
181        InvalidInput { field: String },
182    }
183}
184
185// Validation error subcategory
186pub mod validation {
187    use super::*;
188
189    #[derive(Debug, Error)]
190    pub enum ValidationError {
191        #[error("Invalid configuration: {parameter}")]
192        Config { parameter: String },
193
194        #[error("Invalid input: {field}")]
195        InvalidInput { field: String },
196
197        #[error("Invalid argument: {argument}")]
198        InvalidArgument { argument: String },
199
200        #[error("Invalid address: {address}")]
201        InvalidAddress { address: String },
202
203        #[error("Deposit address validation failed: {address}")]
204        DepositAddressValidationFailed { address: String },
205
206        #[error("Deposit transaction not found in network: {txid}")]
207        DepositTransactionNotFoundInNetwork { txid: String },
208
209        #[error("Invalid keyshare config with id: {config_id}")]
210        InvalidKeyshareConfig { config_id: String },
211
212        #[error("Invalid sequence number: {sequence}")]
213        InvalidSequence { sequence: u64 },
214
215        #[error("Invalid UUID: {0}")]
216        InvalidUuid(#[from] uuid::Error),
217
218        #[error("Invalid Bolt11 invoice: {0}")]
219        InvalidBolt11Invoice(String),
220
221        #[cfg(feature = "integration-tests")]
222        #[error("Transfer claim did not update balance")]
223        TransferClaimDidNotUpdateBalance,
224    }
225}
226
227// Transaction error subcategory
228pub mod transaction {
229    use super::*;
230
231    #[derive(Debug, Error)]
232    pub enum TransactionError {
233        #[error("Transfer failed: {reason}")]
234        Transfer { reason: String },
235
236        #[error("Invalid bitcoin transaction: {0}")]
237        InvalidBitcoinTransaction(String),
238
239        #[error("Invalid token transaction: {0}")]
240        InvalidTokenTransaction(String),
241
242        #[error("Invalid deposit transaction: {0}")]
243        InvalidDepositTx(String),
244    }
245}
246
247// IO error subcategory
248pub mod io_error {
249
250    use super::*;
251
252    #[derive(Debug, Error)]
253    pub enum IoError {
254        #[error("IO error: {0}")]
255        Io(#[from] io::Error),
256
257        #[error("JSON serialization error: {0}")]
258        SerdeJson(#[from] serde_json::Error),
259
260        #[error("Hex decoding error: {0}")]
261        Decoding(#[from] hex::FromHexError),
262
263        #[error("Bolt11 invoice decoding error: {0}")]
264        Bolt11InvoiceDecoding(String),
265    }
266}
267
268// Internal error subcategory
269pub mod internal {
270    use super::*;
271
272    #[derive(Debug, Error)]
273    pub enum InternalError {
274        #[error("Initialization failed: {component}")]
275        Initialization { component: String },
276
277        #[error("Tree creation failed: {reason}")]
278        TreeCreation { reason: String },
279
280        #[error("Test database query error: {query}")]
281        TestDatabaseQuery { query: String },
282
283        #[error("Async task failed: {task}")]
284        AsyncTask { task: String },
285    }
286}
287
288// Implement From traits for common conversions
289impl From<String> for SparkSdkError {
290    fn from(s: String) -> Self {
291        Self::General(s)
292    }
293}
294
295impl From<&str> for SparkSdkError {
296    fn from(s: &str) -> Self {
297        Self::General(s.to_string())
298    }
299}
300
301// Add direct conversion from tonic transport errors
302impl From<tonic::transport::Error> for SparkSdkError {
303    fn from(err: tonic::transport::Error) -> Self {
304        Self::Network(NetworkError::Transport(err))
305    }
306}
307
308// Add direct conversion from secp256k1 errors
309impl From<secp256k1::Error> for SparkSdkError {
310    fn from(err: secp256k1::Error) -> Self {
311        Self::Crypto(CryptoError::Secp256k1(err))
312    }
313}
314
315// Add manual conversion from ParseOrSemanticError
316impl From<ParseOrSemanticError> for IoError {
317    fn from(err: ParseOrSemanticError) -> Self {
318        Self::Bolt11InvoiceDecoding(err.to_string())
319    }
320}