1use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10use bsv::wallet::interfaces::{ReviewActionResult, SendWithResult};
11use bsv::wallet::types::OutpointString;
12
13pub type WalletResult<T> = Result<T, WalletError>;
15
16#[derive(Debug, Error)]
21pub enum WalletError {
22 #[error("WERR_INTERNAL: {0}")]
24 Internal(String),
25
26 #[error("WERR_INVALID_PARAMETER: The {parameter} parameter must be {must_be}")]
28 InvalidParameter {
29 parameter: String,
31 must_be: String,
33 },
34
35 #[error("WERR_NOT_IMPLEMENTED: {0}")]
37 NotImplemented(String),
38
39 #[error("WERR_BAD_REQUEST: {0}")]
41 BadRequest(String),
42
43 #[error("WERR_UNAUTHORIZED: {0}")]
45 Unauthorized(String),
46
47 #[error("WERR_NOT_ACTIVE: {0}")]
49 NotActive(String),
50
51 #[error("WERR_INVALID_OPERATION: {0}")]
53 InvalidOperation(String),
54
55 #[error("WERR_MISSING_PARAMETER: The required {0} parameter is missing.")]
57 MissingParameter(String),
58
59 #[error("WERR_INSUFFICIENT_FUNDS: {message}")]
61 InsufficientFunds {
62 message: String,
64 total_satoshis_needed: i64,
66 more_satoshis_needed: i64,
68 },
69
70 #[error("WERR_BROADCAST_UNAVAILABLE: Unable to broadcast transaction at this time.")]
72 BroadcastUnavailable,
73
74 #[error("WERR_NETWORK_CHAIN: {0}")]
76 NetworkChain(String),
77
78 #[error("WERR_INVALID_PUBLIC_KEY: {message}")]
80 InvalidPublicKey {
81 message: String,
83 key: String,
85 },
86
87 #[error("WERR_REVIEW_ACTIONS: {message}")]
89 ReviewActions {
90 message: String,
92 review_action_results: Vec<ReviewActionResult>,
94 send_with_results: Vec<SendWithResult>,
96 txid: Option<String>,
98 tx: Option<Vec<u8>>,
100 no_send_change: Vec<OutpointString>,
102 },
103
104 #[error("WERR_INVALID_MERKLE_ROOT: {message}")]
106 InvalidMerkleRoot {
107 message: String,
109 block_hash: String,
111 block_height: u32,
113 merkle_root: String,
115 txid: Option<String>,
117 },
118
119 #[error("WERR_INTERNAL: {0}")]
121 Sqlx(#[from] sqlx::Error),
122
123 #[error("WERR_INTERNAL: {0}")]
125 Io(#[from] std::io::Error),
126
127 #[error("WERR_INTERNAL: {0}")]
129 SerdeJson(#[from] serde_json::Error),
130}
131
132impl WalletError {
133 pub fn code(&self) -> &'static str {
135 match self {
136 Self::Internal(_) | Self::Sqlx(_) | Self::Io(_) | Self::SerdeJson(_) => "WERR_INTERNAL",
137 Self::InvalidParameter { .. } => "WERR_INVALID_PARAMETER",
138 Self::NotImplemented(_) => "WERR_NOT_IMPLEMENTED",
139 Self::BadRequest(_) => "WERR_BAD_REQUEST",
140 Self::Unauthorized(_) => "WERR_UNAUTHORIZED",
141 Self::NotActive(_) => "WERR_NOT_ACTIVE",
142 Self::InvalidOperation(_) => "WERR_INVALID_OPERATION",
143 Self::MissingParameter(_) => "WERR_MISSING_PARAMETER",
144 Self::InsufficientFunds { .. } => "WERR_INSUFFICIENT_FUNDS",
145 Self::BroadcastUnavailable => "WERR_BROADCAST_UNAVAILABLE",
146 Self::NetworkChain(_) => "WERR_NETWORK_CHAIN",
147 Self::InvalidPublicKey { .. } => "WERR_INVALID_PUBLIC_KEY",
148 Self::ReviewActions { .. } => "WERR_REVIEW_ACTIONS",
149 Self::InvalidMerkleRoot { .. } => "WERR_INVALID_MERKLE_ROOT",
150 }
151 }
152
153 pub fn to_wallet_error_object(&self) -> WalletErrorObject {
157 let mut obj = WalletErrorObject {
158 is_error: true,
159 name: self.code().to_string(),
160 message: self.to_string(),
161 code: None,
162 parameter: None,
163 total_satoshis_needed: None,
164 more_satoshis_needed: None,
165 review_action_results: None,
166 send_with_results: None,
167 txid: None,
168 tx: None,
169 no_send_change: None,
170 block_hash: None,
171 block_height: None,
172 merkle_root: None,
173 key: None,
174 };
175
176 match self {
177 Self::InvalidParameter { parameter, .. } => {
178 obj.parameter = Some(parameter.clone());
179 }
180 Self::MissingParameter(p) => {
181 obj.parameter = Some(p.clone());
182 }
183 Self::InsufficientFunds {
184 total_satoshis_needed,
185 more_satoshis_needed,
186 ..
187 } => {
188 obj.total_satoshis_needed = Some(*total_satoshis_needed);
189 obj.more_satoshis_needed = Some(*more_satoshis_needed);
190 }
191 Self::InvalidPublicKey { key, .. } => {
192 obj.key = Some(key.clone());
193 }
194 Self::ReviewActions {
195 review_action_results,
196 send_with_results,
197 txid,
198 tx,
199 no_send_change,
200 ..
201 } => {
202 obj.code = Some(5);
203 obj.review_action_results = Some(review_action_results.clone());
204 obj.send_with_results = Some(send_with_results.clone());
205 obj.txid = txid.clone();
206 obj.tx = tx.clone();
207 obj.no_send_change = Some(no_send_change.clone());
208 }
209 Self::InvalidMerkleRoot {
210 block_hash,
211 block_height,
212 merkle_root,
213 txid,
214 ..
215 } => {
216 obj.code = Some(8);
217 obj.block_hash = Some(block_hash.clone());
218 obj.block_height = Some(*block_height);
219 obj.merkle_root = Some(merkle_root.clone());
220 obj.txid = txid.clone();
221 }
222 _ => {}
223 }
224
225 obj
226 }
227}
228
229pub fn wallet_error_from_object(obj: WalletErrorObject) -> WalletError {
235 match obj.name.as_str() {
236 "WERR_INVALID_PARAMETER" => WalletError::InvalidParameter {
237 parameter: obj.parameter.unwrap_or_default(),
238 must_be: obj.message,
239 },
240 "WERR_NOT_IMPLEMENTED" => WalletError::NotImplemented(obj.message),
241 "WERR_BAD_REQUEST" => WalletError::BadRequest(obj.message),
242 "WERR_UNAUTHORIZED" => WalletError::Unauthorized(obj.message),
243 "WERR_NOT_ACTIVE" => WalletError::NotActive(obj.message),
244 "WERR_INVALID_OPERATION" => WalletError::InvalidOperation(obj.message),
245 "WERR_MISSING_PARAMETER" => {
246 WalletError::MissingParameter(obj.parameter.unwrap_or_else(|| obj.message.clone()))
247 }
248 "WERR_INSUFFICIENT_FUNDS" => WalletError::InsufficientFunds {
249 message: obj.message,
250 total_satoshis_needed: obj.total_satoshis_needed.unwrap_or(0),
251 more_satoshis_needed: obj.more_satoshis_needed.unwrap_or(0),
252 },
253 "WERR_BROADCAST_UNAVAILABLE" => WalletError::BroadcastUnavailable,
254 "WERR_NETWORK_CHAIN" => WalletError::NetworkChain(obj.message),
255 "WERR_INVALID_PUBLIC_KEY" => WalletError::InvalidPublicKey {
256 message: obj.message,
257 key: obj.key.or(obj.parameter).unwrap_or_default(),
258 },
259 "WERR_REVIEW_ACTIONS" => WalletError::ReviewActions {
260 message: obj.message,
261 review_action_results: obj.review_action_results.unwrap_or_default(),
262 send_with_results: obj.send_with_results.unwrap_or_default(),
263 txid: obj.txid,
264 tx: obj.tx,
265 no_send_change: obj.no_send_change.unwrap_or_default(),
266 },
267 "WERR_INVALID_MERKLE_ROOT" => WalletError::InvalidMerkleRoot {
268 message: obj.message,
269 block_hash: obj.block_hash.unwrap_or_default(),
270 block_height: obj.block_height.unwrap_or(0),
271 merkle_root: obj.merkle_root.unwrap_or_default(),
272 txid: obj.txid,
273 },
274 _ => WalletError::Internal(obj.message),
275 }
276}
277
278#[derive(Debug, Serialize, Deserialize)]
280#[serde(rename_all = "camelCase")]
281pub struct WalletErrorObject {
282 pub is_error: bool,
284 pub name: String,
286 pub message: String,
288 #[serde(skip_serializing_if = "Option::is_none")]
290 pub code: Option<u8>,
291 #[serde(skip_serializing_if = "Option::is_none")]
293 pub parameter: Option<String>,
294 #[serde(skip_serializing_if = "Option::is_none")]
296 pub total_satoshis_needed: Option<i64>,
297 #[serde(skip_serializing_if = "Option::is_none")]
299 pub more_satoshis_needed: Option<i64>,
300 #[serde(skip_serializing_if = "Option::is_none")]
302 pub review_action_results: Option<Vec<ReviewActionResult>>,
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub send_with_results: Option<Vec<SendWithResult>>,
306 #[serde(skip_serializing_if = "Option::is_none")]
308 pub txid: Option<String>,
309 #[serde(skip_serializing_if = "Option::is_none")]
311 pub tx: Option<Vec<u8>>,
312 #[serde(skip_serializing_if = "Option::is_none")]
314 pub no_send_change: Option<Vec<OutpointString>>,
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub block_hash: Option<String>,
318 #[serde(skip_serializing_if = "Option::is_none")]
320 pub block_height: Option<u32>,
321 #[serde(skip_serializing_if = "Option::is_none")]
323 pub merkle_root: Option<String>,
324 #[serde(skip_serializing_if = "Option::is_none")]
326 pub key: Option<String>,
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use bsv::wallet::interfaces::{
333 ActionResultStatus, ReviewActionResult, ReviewActionResultStatus, SendWithResult,
334 };
335
336 #[test]
337 fn test_review_actions_error_roundtrip() {
338 let err = WalletError::ReviewActions {
339 message: "Undelayed results require review.".to_string(),
340 review_action_results: vec![ReviewActionResult {
341 txid: "aabb".to_string(),
342 status: ReviewActionResultStatus::DoubleSpend,
343 competing_txs: Some(vec!["ccdd".to_string()]),
344 competing_beef: None,
345 }],
346 send_with_results: vec![SendWithResult {
347 txid: "eeff".to_string(),
348 status: ActionResultStatus::Sending,
349 }],
350 txid: Some("aabb".to_string()),
351 tx: None,
352 no_send_change: vec![],
353 };
354 assert_eq!(err.code(), "WERR_REVIEW_ACTIONS");
355 let obj = err.to_wallet_error_object();
356 assert_eq!(obj.code, Some(5));
357 assert!(obj.review_action_results.is_some());
358 let err2 = wallet_error_from_object(obj);
359 assert_eq!(err2.code(), "WERR_REVIEW_ACTIONS");
360 }
361
362 #[test]
363 fn test_invalid_merkle_root_error_roundtrip() {
364 let err = WalletError::InvalidMerkleRoot {
365 message: "Invalid merkleRoot abc for block def at height 100".to_string(),
366 block_hash: "def".to_string(),
367 block_height: 100,
368 merkle_root: "abc".to_string(),
369 txid: Some("1234".to_string()),
370 };
371 assert_eq!(err.code(), "WERR_INVALID_MERKLE_ROOT");
372 let obj = err.to_wallet_error_object();
373 assert_eq!(obj.code, Some(8));
374 assert_eq!(obj.block_hash.as_deref(), Some("def"));
375 let err2 = wallet_error_from_object(obj);
376 assert_eq!(err2.code(), "WERR_INVALID_MERKLE_ROOT");
377 }
378
379 #[test]
380 fn test_invalid_public_key_key_field_roundtrip() {
381 let err = WalletError::InvalidPublicKey {
382 message: "not on curve".to_string(),
383 key: "02abcdef".to_string(),
384 };
385 let obj = err.to_wallet_error_object();
386 assert_eq!(obj.key.as_deref(), Some("02abcdef"));
387 let json = serde_json::to_string(&obj).unwrap();
388 assert!(json.contains("\"key\":\"02abcdef\""));
389 let obj2: WalletErrorObject = serde_json::from_str(&json).unwrap();
390 let err2 = wallet_error_from_object(obj2);
391 match err2 {
392 WalletError::InvalidPublicKey { key, .. } => assert_eq!(key, "02abcdef"),
393 _ => panic!("Expected InvalidPublicKey"),
394 }
395 }
396}