Skip to main content

bsv/wallet/
interfaces.rs

1//! WalletInterface trait and all arg/result structs.
2//!
3//! Defines the contract that all wallet implementations must satisfy.
4//! Translated from Go SDK wallet/interfaces.go and TS SDK Wallet.interfaces.ts.
5//! Uses #[async_trait] for object safety -- enables `dyn WalletInterface`.
6
7use std::collections::HashMap;
8
9use async_trait::async_trait;
10
11use crate::primitives::public_key::PublicKey;
12use crate::wallet::error::WalletError;
13use crate::wallet::types::{
14    BasketStringUnder300Bytes, BooleanDefaultFalse, BooleanDefaultTrue, Counterparty,
15    DescriptionString5to50Bytes, LabelStringUnder300Bytes, OutpointString,
16    OutputTagStringUnder300Bytes, PositiveIntegerDefault10Max10000, PositiveIntegerOrZero,
17    Protocol, SatoshiValue, TXIDHexString,
18};
19
20// ---------------------------------------------------------------------------
21// Serde helper modules (only compiled with "network" feature)
22// ---------------------------------------------------------------------------
23
24/// Serde helpers for custom JSON serialization of wallet types.
25/// Gated behind the "network" feature since serde is an optional dependency.
26#[cfg(feature = "network")]
27pub(crate) mod serde_helpers {
28    use crate::primitives::public_key::PublicKey;
29
30    /// Serialize/deserialize PublicKey as DER hex string.
31    pub mod public_key_hex {
32        use super::PublicKey;
33        use serde::{self, Deserialize, Deserializer, Serializer};
34
35        pub fn serialize<S>(pk: &PublicKey, serializer: S) -> Result<S::Ok, S::Error>
36        where
37            S: Serializer,
38        {
39            serializer.serialize_str(&pk.to_der_hex())
40        }
41
42        pub fn deserialize<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
43        where
44            D: Deserializer<'de>,
45        {
46            let s = String::deserialize(deserializer)?;
47            PublicKey::from_string(&s).map_err(serde::de::Error::custom)
48        }
49    }
50
51    /// Serialize/deserialize `Option<PublicKey>` as optional DER hex string.
52    #[allow(dead_code)]
53    pub mod option_public_key_hex {
54        use super::PublicKey;
55        use serde::{self, Deserialize, Deserializer, Serializer};
56
57        pub fn serialize<S>(pk: &Option<PublicKey>, serializer: S) -> Result<S::Ok, S::Error>
58        where
59            S: Serializer,
60        {
61            match pk {
62                Some(pk) => serializer.serialize_str(&pk.to_der_hex()),
63                None => serializer.serialize_none(),
64            }
65        }
66
67        pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<PublicKey>, D::Error>
68        where
69            D: Deserializer<'de>,
70        {
71            let opt: Option<String> = Option::deserialize(deserializer)?;
72            match opt {
73                Some(s) if !s.is_empty() => PublicKey::from_string(&s)
74                    .map(Some)
75                    .map_err(serde::de::Error::custom),
76                _ => Ok(None),
77            }
78        }
79    }
80
81    /// Serialize/deserialize `Vec<PublicKey>` as array of DER hex strings.
82    pub mod vec_public_key_hex {
83        use super::PublicKey;
84        use serde::ser::SerializeSeq;
85        use serde::{self, Deserialize, Deserializer, Serializer};
86
87        pub fn serialize<S>(pks: &[PublicKey], serializer: S) -> Result<S::Ok, S::Error>
88        where
89            S: Serializer,
90        {
91            let mut seq = serializer.serialize_seq(Some(pks.len()))?;
92            for pk in pks {
93                seq.serialize_element(&pk.to_der_hex())?;
94            }
95            seq.end()
96        }
97
98        pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<PublicKey>, D::Error>
99        where
100            D: Deserializer<'de>,
101        {
102            let strs: Vec<String> = Vec::deserialize(deserializer)?;
103            strs.iter()
104                .map(|s| PublicKey::from_string(s).map_err(serde::de::Error::custom))
105                .collect()
106        }
107    }
108
109    /// Serialize/deserialize [u8; 32] as base64 string (matches Go SDK Bytes32Base64).
110    pub mod bytes32_base64 {
111        use serde::{self, Deserialize, Deserializer, Serializer};
112
113        pub fn serialize<S>(bytes: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
114        where
115            S: Serializer,
116        {
117            serializer.serialize_str(&base64_encode(bytes))
118        }
119
120        pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
121        where
122            D: Deserializer<'de>,
123        {
124            let s = String::deserialize(deserializer)?;
125            let decoded = base64_decode(&s).map_err(serde::de::Error::custom)?;
126            if decoded.len() > 32 {
127                return Err(serde::de::Error::custom(
128                    "base64 decoded value exceeds 32 bytes",
129                ));
130            }
131            let mut buf = [0u8; 32];
132            buf[..decoded.len()].copy_from_slice(&decoded);
133            Ok(buf)
134        }
135
136        /// Encode variable-length bytes as base64 (used by bytes_as_base64 module).
137        pub(super) fn base64_encode_vec(data: &[u8]) -> String {
138            base64_encode(data)
139        }
140
141        /// Decode base64 string to variable-length bytes (used by bytes_as_base64 module).
142        pub(super) fn base64_decode_vec(s: &str) -> Result<Vec<u8>, String> {
143            base64_decode(s)
144        }
145
146        fn base64_encode(data: &[u8]) -> String {
147            const CHARS: &[u8] =
148                b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
149            let mut result = String::new();
150            let chunks = data.chunks(3);
151            for chunk in chunks {
152                let b0 = chunk[0] as u32;
153                let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
154                let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
155                let triple = (b0 << 16) | (b1 << 8) | b2;
156                result.push(CHARS[((triple >> 18) & 0x3F) as usize] as char);
157                result.push(CHARS[((triple >> 12) & 0x3F) as usize] as char);
158                if chunk.len() > 1 {
159                    result.push(CHARS[((triple >> 6) & 0x3F) as usize] as char);
160                } else {
161                    result.push('=');
162                }
163                if chunk.len() > 2 {
164                    result.push(CHARS[(triple & 0x3F) as usize] as char);
165                } else {
166                    result.push('=');
167                }
168            }
169            result
170        }
171
172        fn base64_decode(s: &str) -> Result<Vec<u8>, String> {
173            fn char_to_val(c: u8) -> Result<u8, String> {
174                match c {
175                    b'A'..=b'Z' => Ok(c - b'A'),
176                    b'a'..=b'z' => Ok(c - b'a' + 26),
177                    b'0'..=b'9' => Ok(c - b'0' + 52),
178                    b'+' => Ok(62),
179                    b'/' => Ok(63),
180                    _ => Err(format!("invalid base64 character: {}", c as char)),
181                }
182            }
183            let bytes = s.as_bytes();
184            let mut result = Vec::new();
185            let mut i = 0;
186            while i < bytes.len() {
187                if bytes[i] == b'=' {
188                    break;
189                }
190                let a = char_to_val(bytes[i])?;
191                let b = if i + 1 < bytes.len() && bytes[i + 1] != b'=' {
192                    char_to_val(bytes[i + 1])?
193                } else {
194                    0
195                };
196                let c = if i + 2 < bytes.len() && bytes[i + 2] != b'=' {
197                    char_to_val(bytes[i + 2])?
198                } else {
199                    0
200                };
201                let d = if i + 3 < bytes.len() && bytes[i + 3] != b'=' {
202                    char_to_val(bytes[i + 3])?
203                } else {
204                    0
205                };
206                let triple =
207                    ((a as u32) << 18) | ((b as u32) << 12) | ((c as u32) << 6) | (d as u32);
208                result.push(((triple >> 16) & 0xFF) as u8);
209                if i + 2 < bytes.len() && bytes[i + 2] != b'=' {
210                    result.push(((triple >> 8) & 0xFF) as u8);
211                }
212                if i + 3 < bytes.len() && bytes[i + 3] != b'=' {
213                    result.push((triple & 0xFF) as u8);
214                }
215                i += 4;
216            }
217            Ok(result)
218        }
219    }
220
221    /// Serialize/deserialize `Vec<u8>` as base64 string.
222    ///
223    /// Matches Go's default `json.Marshal` for `[]byte` and TS SDK `Base64String`.
224    /// Used for `Payment.derivation_prefix` and `Payment.derivation_suffix`.
225    pub mod bytes_as_base64 {
226        use serde::{self, Deserialize, Deserializer, Serializer};
227
228        pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
229        where
230            S: Serializer,
231        {
232            serializer.serialize_str(&super::bytes32_base64::base64_encode_vec(bytes))
233        }
234
235        pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
236        where
237            D: Deserializer<'de>,
238        {
239            let s = String::deserialize(deserializer)?;
240            super::bytes32_base64::base64_decode_vec(&s).map_err(serde::de::Error::custom)
241        }
242    }
243
244    /// Serialize/deserialize `Vec<u8>` as JSON array of numbers (matches Go SDK BytesList).
245    pub mod bytes_as_array {
246        use serde::{Deserialize, Deserializer, Serializer};
247
248        pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
249        where
250            S: Serializer,
251        {
252            serializer.collect_seq(bytes.iter())
253        }
254
255        pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
256        where
257            D: Deserializer<'de>,
258        {
259            Vec::<u8>::deserialize(deserializer)
260        }
261    }
262
263    /// Serialize/deserialize `Option<Vec<u8>>` as optional JSON array of numbers.
264    pub mod option_bytes_as_array {
265        use serde::{Deserialize, Deserializer, Serializer};
266
267        pub fn serialize<S>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
268        where
269            S: Serializer,
270        {
271            match bytes {
272                Some(b) => serializer.collect_seq(b.iter()),
273                None => serializer.serialize_none(),
274            }
275        }
276
277        pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
278        where
279            D: Deserializer<'de>,
280        {
281            Option::<Vec<u8>>::deserialize(deserializer)
282        }
283    }
284
285    /// Serialize/deserialize `Vec<u8>` as hex string (matches Go SDK BytesHex).
286    pub mod bytes_as_hex {
287        use serde::{self, Deserialize, Deserializer, Serializer};
288
289        pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
290        where
291            S: Serializer,
292        {
293            serializer.serialize_str(&to_hex(bytes))
294        }
295
296        pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
297        where
298            D: Deserializer<'de>,
299        {
300            let s = String::deserialize(deserializer)?;
301            from_hex(&s).map_err(serde::de::Error::custom)
302        }
303
304        fn to_hex(bytes: &[u8]) -> String {
305            const HEX: &[u8; 16] = b"0123456789abcdef";
306            let mut s = String::with_capacity(bytes.len() * 2);
307            for &b in bytes {
308                s.push(HEX[(b >> 4) as usize] as char);
309                s.push(HEX[(b & 0xf) as usize] as char);
310            }
311            s
312        }
313
314        pub(crate) fn from_hex(s: &str) -> Result<Vec<u8>, String> {
315            if !s.len().is_multiple_of(2) {
316                return Err("hex string has odd length".to_string());
317            }
318            let bytes = s.as_bytes();
319            let mut result = Vec::with_capacity(bytes.len() / 2);
320            for chunk in bytes.chunks(2) {
321                let hi = hex_val(chunk[0])?;
322                let lo = hex_val(chunk[1])?;
323                result.push((hi << 4) | lo);
324            }
325            Ok(result)
326        }
327
328        fn hex_val(b: u8) -> Result<u8, String> {
329            match b {
330                b'0'..=b'9' => Ok(b - b'0'),
331                b'a'..=b'f' => Ok(b - b'a' + 10),
332                b'A'..=b'F' => Ok(b - b'A' + 10),
333                _ => Err(format!("invalid hex character: {}", b as char)),
334            }
335        }
336    }
337
338    /// Serialize/deserialize `Option<Vec<u8>>` as optional hex string.
339    pub mod option_bytes_as_hex {
340        use serde::{self, Deserialize, Deserializer, Serializer};
341
342        pub fn serialize<S>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
343        where
344            S: Serializer,
345        {
346            match bytes {
347                Some(b) => super::bytes_as_hex::serialize(b, serializer),
348                None => serializer.serialize_none(),
349            }
350        }
351
352        pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
353        where
354            D: Deserializer<'de>,
355        {
356            let opt: Option<String> = Option::deserialize(deserializer)?;
357            match opt {
358                Some(s) if !s.is_empty() => super::bytes_as_hex::from_hex(&s)
359                    .map(Some)
360                    .map_err(serde::de::Error::custom),
361                _ => Ok(None),
362            }
363        }
364    }
365}
366
367// ---------------------------------------------------------------------------
368// Enums
369// ---------------------------------------------------------------------------
370
371/// Current state of a transaction action.
372#[derive(Clone, Debug, PartialEq, Eq)]
373#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
374#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
375pub enum ActionStatus {
376    Completed,
377    Unprocessed,
378    Sending,
379    Unproven,
380    Unsigned,
381    #[cfg_attr(feature = "network", serde(rename = "nosend"))]
382    NoSend,
383    #[cfg_attr(feature = "network", serde(rename = "nonfinal"))]
384    NonFinal,
385    Failed,
386}
387
388impl ActionStatus {
389    pub fn as_str(&self) -> &'static str {
390        match self {
391            ActionStatus::Completed => "completed",
392            ActionStatus::Unprocessed => "unprocessed",
393            ActionStatus::Sending => "sending",
394            ActionStatus::Unproven => "unproven",
395            ActionStatus::Unsigned => "unsigned",
396            ActionStatus::NoSend => "nosend",
397            ActionStatus::NonFinal => "nonfinal",
398            ActionStatus::Failed => "failed",
399        }
400    }
401}
402
403/// Status of a transaction result (subset of ActionStatus).
404#[derive(Clone, Debug, PartialEq, Eq)]
405#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
406#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
407pub enum ActionResultStatus {
408    Unproven,
409    Sending,
410    Failed,
411}
412
413impl ActionResultStatus {
414    pub fn as_str(&self) -> &'static str {
415        match self {
416            ActionResultStatus::Unproven => "unproven",
417            ActionResultStatus::Sending => "sending",
418            ActionResultStatus::Failed => "failed",
419        }
420    }
421}
422
423/// How multiple criteria are combined in queries.
424#[derive(Clone, Debug, PartialEq, Eq)]
425#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
426#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
427pub enum QueryMode {
428    Any,
429    All,
430}
431
432impl QueryMode {
433    pub fn as_str(&self) -> &'static str {
434        match self {
435            QueryMode::Any => "any",
436            QueryMode::All => "all",
437        }
438    }
439}
440
441/// What additional data to include with output listings.
442#[derive(Clone, Debug, PartialEq, Eq)]
443#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
444pub enum OutputInclude {
445    #[cfg_attr(feature = "network", serde(rename = "locking scripts"))]
446    LockingScripts,
447    #[cfg_attr(feature = "network", serde(rename = "entire transactions"))]
448    EntireTransactions,
449}
450
451impl OutputInclude {
452    pub fn as_str(&self) -> &'static str {
453        match self {
454            OutputInclude::LockingScripts => "locking scripts",
455            OutputInclude::EntireTransactions => "entire transactions",
456        }
457    }
458}
459
460/// Protocol for internalizing transaction outputs.
461#[derive(Clone, Debug, PartialEq, Eq)]
462#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
463pub enum InternalizeProtocol {
464    #[cfg_attr(feature = "network", serde(rename = "wallet payment"))]
465    WalletPayment,
466    #[cfg_attr(feature = "network", serde(rename = "basket insertion"))]
467    BasketInsertion,
468}
469
470impl InternalizeProtocol {
471    pub fn as_str(&self) -> &'static str {
472        match self {
473            InternalizeProtocol::WalletPayment => "wallet payment",
474            InternalizeProtocol::BasketInsertion => "basket insertion",
475        }
476    }
477}
478
479/// Protocol for certificate acquisition.
480#[derive(Clone, Debug, PartialEq, Eq)]
481#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
482#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
483pub enum AcquisitionProtocol {
484    Direct,
485    Issuance,
486}
487
488impl AcquisitionProtocol {
489    pub fn as_str(&self) -> &'static str {
490        match self {
491            AcquisitionProtocol::Direct => "direct",
492            AcquisitionProtocol::Issuance => "issuance",
493        }
494    }
495}
496
497/// Blockchain network type.
498#[derive(Clone, Debug, PartialEq, Eq)]
499#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
500#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
501pub enum Network {
502    Mainnet,
503    Testnet,
504}
505
506impl Network {
507    pub fn as_str(&self) -> &'static str {
508        match self {
509            Network::Mainnet => "mainnet",
510            Network::Testnet => "testnet",
511        }
512    }
513}
514
515/// Trust level for self-referential operations.
516#[derive(Clone, Debug, PartialEq, Eq)]
517#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
518#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
519pub enum TrustSelf {
520    Known,
521}
522
523impl TrustSelf {
524    pub fn as_str(&self) -> &'static str {
525        match self {
526            TrustSelf::Known => "known",
527        }
528    }
529}
530
531// ---------------------------------------------------------------------------
532// Core types: Certificate, CertificateType, SerialNumber, KeyringRevealer
533// ---------------------------------------------------------------------------
534
535/// Newtype wrapper for certificate type identifier (32 bytes).
536/// Serializes as base64 string matching Go SDK Bytes32Base64.
537#[derive(Clone, Debug, PartialEq, Eq, Hash)]
538pub struct CertificateType(pub [u8; 32]);
539
540#[cfg(feature = "network")]
541impl serde::Serialize for CertificateType {
542    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
543        serde_helpers::bytes32_base64::serialize(&self.0, serializer)
544    }
545}
546
547#[cfg(feature = "network")]
548impl<'de> serde::Deserialize<'de> for CertificateType {
549    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
550        serde_helpers::bytes32_base64::deserialize(deserializer).map(CertificateType)
551    }
552}
553
554impl CertificateType {
555    /// Parse a CertificateType from a base64, hex, or raw string.
556    ///
557    /// Accepts base64 (44 chars or ending with '='), hex (64 hex chars),
558    /// or raw byte strings (<=32 bytes).
559    pub fn from_string(s: &str) -> Result<Self, WalletError> {
560        let bytes = if s.len() == 44 || (!s.is_empty() && s.ends_with('=')) {
561            SerialNumber::base64_decode_sn(s)?
562        } else if s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit()) {
563            crate::primitives::utils::from_hex(s)
564                .map_err(|e| WalletError::InvalidParameter(format!("hex: {}", e)))?
565        } else if s.len() <= 32 {
566            let mut buf = [0u8; 32];
567            buf[..s.len()].copy_from_slice(s.as_bytes());
568            return Ok(CertificateType(buf));
569        } else {
570            return Err(WalletError::InvalidParameter(format!(
571                "CertificateType: unsupported string length {}",
572                s.len()
573            )));
574        };
575        if bytes.len() != 32 {
576            return Err(WalletError::InvalidParameter(format!(
577                "CertificateType must decode to 32 bytes, got {}",
578                bytes.len()
579            )));
580        }
581        let mut buf = [0u8; 32];
582        buf.copy_from_slice(&bytes);
583        Ok(CertificateType(buf))
584    }
585
586    pub fn bytes(&self) -> &[u8; 32] {
587        &self.0
588    }
589}
590
591/// Newtype wrapper for certificate serial number (32 bytes).
592/// Serializes as base64 string matching Go SDK Bytes32Base64.
593#[derive(Clone, Debug, PartialEq, Eq, Hash)]
594pub struct SerialNumber(pub [u8; 32]);
595
596#[cfg(feature = "network")]
597impl serde::Serialize for SerialNumber {
598    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
599        serde_helpers::bytes32_base64::serialize(&self.0, serializer)
600    }
601}
602
603#[cfg(feature = "network")]
604impl<'de> serde::Deserialize<'de> for SerialNumber {
605    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
606        serde_helpers::bytes32_base64::deserialize(deserializer).map(SerialNumber)
607    }
608}
609
610impl SerialNumber {
611    /// Parse a SerialNumber from a base64 or hex string.
612    ///
613    /// Accepts:
614    /// - 44-character base64 string (with optional padding, decodes to 32 bytes)
615    /// - 64-character hex string (decodes to 32 bytes)
616    ///
617    /// Returns an error for other formats or if the decoded length is not 32 bytes.
618    pub fn from_string(s: &str) -> Result<Self, WalletError> {
619        let bytes = if s.len() == 44 || (!s.is_empty() && s.ends_with('=')) {
620            // Base64 format (32 bytes -> 44 base64 chars with padding)
621            Self::base64_decode_sn(s)?
622        } else if s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit()) {
623            // Hex format (32 bytes -> 64 hex chars)
624            crate::primitives::utils::from_hex(s)
625                .map_err(|e| WalletError::InvalidParameter(format!("hex: {}", e)))?
626        } else {
627            return Err(WalletError::InvalidParameter(format!(
628                "SerialNumber string must be 44 (base64) or 64 (hex) chars, got {}",
629                s.len()
630            )));
631        };
632        if bytes.len() != 32 {
633            return Err(WalletError::InvalidParameter(
634                "SerialNumber must decode to 32 bytes".into(),
635            ));
636        }
637        let mut buf = [0u8; 32];
638        buf.copy_from_slice(&bytes);
639        Ok(SerialNumber(buf))
640    }
641
642    /// Inline base64 decoder for SerialNumber (self-contained, no cross-module dependency).
643    pub(crate) fn base64_decode_sn(s: &str) -> Result<Vec<u8>, WalletError> {
644        fn b64_val(c: u8) -> Result<u8, WalletError> {
645            match c {
646                b'A'..=b'Z' => Ok(c - b'A'),
647                b'a'..=b'z' => Ok(c - b'a' + 26),
648                b'0'..=b'9' => Ok(c - b'0' + 52),
649                b'+' => Ok(62),
650                b'/' => Ok(63),
651                _ => Err(WalletError::InvalidParameter(format!(
652                    "invalid base64 character: {}",
653                    c as char
654                ))),
655            }
656        }
657        let bytes = s.as_bytes();
658        let mut result = Vec::new();
659        let mut i = 0;
660        while i < bytes.len() {
661            if bytes[i] == b'=' {
662                break;
663            }
664            let a = b64_val(bytes[i])?;
665            let b = if i + 1 < bytes.len() && bytes[i + 1] != b'=' {
666                b64_val(bytes[i + 1])?
667            } else {
668                0
669            };
670            let c = if i + 2 < bytes.len() && bytes[i + 2] != b'=' {
671                b64_val(bytes[i + 2])?
672            } else {
673                0
674            };
675            let d = if i + 3 < bytes.len() && bytes[i + 3] != b'=' {
676                b64_val(bytes[i + 3])?
677            } else {
678                0
679            };
680            let n = (a as u32) << 18 | (b as u32) << 12 | (c as u32) << 6 | (d as u32);
681            result.push((n >> 16) as u8);
682            if i + 2 < bytes.len() && bytes[i + 2] != b'=' {
683                result.push((n >> 8) as u8);
684            }
685            if i + 3 < bytes.len() && bytes[i + 3] != b'=' {
686                result.push(n as u8);
687            }
688            i += 4;
689        }
690        Ok(result)
691    }
692
693    pub fn bytes(&self) -> &[u8; 32] {
694        &self.0
695    }
696}
697
698/// A certificate in the wallet.
699#[derive(Clone, Debug)]
700#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
701#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
702pub struct Certificate {
703    #[cfg_attr(feature = "network", serde(rename = "type"))]
704    pub cert_type: CertificateType,
705    pub serial_number: SerialNumber,
706    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
707    pub subject: PublicKey,
708    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
709    pub certifier: PublicKey,
710    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
711    pub revocation_outpoint: Option<String>,
712    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
713    pub fields: Option<HashMap<String, String>>,
714    #[cfg_attr(
715        feature = "network",
716        serde(with = "serde_helpers::option_bytes_as_hex")
717    )]
718    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
719    pub signature: Option<Vec<u8>>,
720}
721
722/// A partial certificate where all fields are optional.
723/// Used for ProveCertificateArgs to match TS SDK's `Partial<WalletCertificate>`.
724#[derive(Clone, Debug)]
725#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
726#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
727pub struct PartialCertificate {
728    #[cfg_attr(feature = "network", serde(rename = "type"))]
729    #[cfg_attr(
730        feature = "network",
731        serde(default, skip_serializing_if = "Option::is_none")
732    )]
733    pub cert_type: Option<CertificateType>,
734    #[cfg_attr(
735        feature = "network",
736        serde(default, skip_serializing_if = "Option::is_none")
737    )]
738    pub serial_number: Option<SerialNumber>,
739    #[cfg_attr(
740        feature = "network",
741        serde(with = "serde_helpers::option_public_key_hex")
742    )]
743    #[cfg_attr(feature = "network", serde(default))]
744    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
745    pub subject: Option<PublicKey>,
746    #[cfg_attr(
747        feature = "network",
748        serde(with = "serde_helpers::option_public_key_hex")
749    )]
750    #[cfg_attr(feature = "network", serde(default))]
751    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
752    pub certifier: Option<PublicKey>,
753    #[cfg_attr(
754        feature = "network",
755        serde(default, skip_serializing_if = "Option::is_none")
756    )]
757    pub revocation_outpoint: Option<String>,
758    #[cfg_attr(
759        feature = "network",
760        serde(default, skip_serializing_if = "Option::is_none")
761    )]
762    pub fields: Option<HashMap<String, String>>,
763    #[cfg_attr(
764        feature = "network",
765        serde(with = "serde_helpers::option_bytes_as_hex")
766    )]
767    #[cfg_attr(feature = "network", serde(default))]
768    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
769    pub signature: Option<Vec<u8>>,
770}
771
772impl From<Certificate> for PartialCertificate {
773    fn from(c: Certificate) -> Self {
774        PartialCertificate {
775            cert_type: Some(c.cert_type),
776            serial_number: Some(c.serial_number),
777            subject: Some(c.subject),
778            certifier: Some(c.certifier),
779            revocation_outpoint: c.revocation_outpoint,
780            fields: c.fields,
781            signature: c.signature,
782        }
783    }
784}
785
786/// Identifies who reveals a keyring.
787#[derive(Clone, Debug)]
788pub enum KeyringRevealer {
789    /// The certifier reveals the keyring.
790    Certifier,
791    /// A specific public key reveals the keyring.
792    PubKey(PublicKey),
793}
794
795#[cfg(feature = "network")]
796impl serde::Serialize for KeyringRevealer {
797    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
798        match self {
799            KeyringRevealer::Certifier => serializer.serialize_str("certifier"),
800            KeyringRevealer::PubKey(pk) => serializer.serialize_str(&pk.to_der_hex()),
801        }
802    }
803}
804
805#[cfg(feature = "network")]
806impl<'de> serde::Deserialize<'de> for KeyringRevealer {
807    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
808        let s = String::deserialize(deserializer)?;
809        if s == "certifier" || s.is_empty() {
810            Ok(KeyringRevealer::Certifier)
811        } else {
812            PublicKey::from_string(&s)
813                .map(KeyringRevealer::PubKey)
814                .map_err(serde::de::Error::custom)
815        }
816    }
817}
818
819// ---------------------------------------------------------------------------
820// Action types (CreateAction, SignAction, AbortAction)
821// ---------------------------------------------------------------------------
822
823/// An input to be spent in a new transaction.
824#[derive(Clone, Debug)]
825#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
826#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
827pub struct CreateActionInput {
828    pub outpoint: OutpointString,
829    pub input_description: String,
830    #[cfg_attr(
831        feature = "network",
832        serde(with = "serde_helpers::option_bytes_as_hex")
833    )]
834    #[cfg_attr(
835        feature = "network",
836        serde(skip_serializing_if = "Option::is_none", default)
837    )]
838    pub unlocking_script: Option<Vec<u8>>,
839    #[cfg_attr(
840        feature = "network",
841        serde(default, skip_serializing_if = "Option::is_none")
842    )]
843    pub unlocking_script_length: Option<u32>,
844    #[cfg_attr(
845        feature = "network",
846        serde(default, skip_serializing_if = "Option::is_none")
847    )]
848    pub sequence_number: Option<u32>,
849}
850
851/// An output to be created in a new transaction.
852#[derive(Clone, Debug)]
853#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
854#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
855pub struct CreateActionOutput {
856    #[cfg_attr(
857        feature = "network",
858        serde(with = "serde_helpers::option_bytes_as_hex")
859    )]
860    #[cfg_attr(
861        feature = "network",
862        serde(skip_serializing_if = "Option::is_none", default)
863    )]
864    pub locking_script: Option<Vec<u8>>,
865    pub satoshis: SatoshiValue,
866    pub output_description: String,
867    #[cfg_attr(
868        feature = "network",
869        serde(default, skip_serializing_if = "Option::is_none")
870    )]
871    pub basket: Option<BasketStringUnder300Bytes>,
872    #[cfg_attr(
873        feature = "network",
874        serde(default, skip_serializing_if = "Option::is_none")
875    )]
876    pub custom_instructions: Option<String>,
877    #[cfg_attr(
878        feature = "network",
879        serde(skip_serializing_if = "Vec::is_empty", default)
880    )]
881    pub tags: Vec<OutputTagStringUnder300Bytes>,
882}
883
884/// Optional parameters for creating a new transaction.
885#[derive(Clone, Debug, Default)]
886#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
887#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
888pub struct CreateActionOptions {
889    #[cfg_attr(feature = "network", serde(default))]
890    pub sign_and_process: BooleanDefaultTrue,
891    #[cfg_attr(feature = "network", serde(default))]
892    pub accept_delayed_broadcast: BooleanDefaultTrue,
893    #[cfg_attr(
894        feature = "network",
895        serde(default, skip_serializing_if = "Option::is_none")
896    )]
897    pub trust_self: Option<TrustSelf>,
898    #[cfg_attr(
899        feature = "network",
900        serde(skip_serializing_if = "Vec::is_empty", default)
901    )]
902    pub known_txids: Vec<TXIDHexString>,
903    #[cfg_attr(feature = "network", serde(default))]
904    pub return_txid_only: BooleanDefaultFalse,
905    #[cfg_attr(feature = "network", serde(default))]
906    pub no_send: BooleanDefaultFalse,
907    #[cfg_attr(
908        feature = "network",
909        serde(skip_serializing_if = "Vec::is_empty", default)
910    )]
911    pub no_send_change: Vec<OutpointString>,
912    #[cfg_attr(
913        feature = "network",
914        serde(skip_serializing_if = "Vec::is_empty", default)
915    )]
916    pub send_with: Vec<TXIDHexString>,
917    #[cfg_attr(feature = "network", serde(default))]
918    pub randomize_outputs: BooleanDefaultTrue,
919}
920
921/// Arguments for creating a new transaction.
922#[derive(Clone, Debug)]
923#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
924#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
925pub struct CreateActionArgs {
926    pub description: DescriptionString5to50Bytes,
927    #[cfg_attr(
928        feature = "network",
929        serde(with = "serde_helpers::option_bytes_as_array")
930    )]
931    #[cfg_attr(
932        feature = "network",
933        serde(skip_serializing_if = "Option::is_none", default)
934    )]
935    #[cfg_attr(feature = "network", serde(rename = "inputBEEF"))]
936    pub input_beef: Option<Vec<u8>>,
937    #[cfg_attr(
938        feature = "network",
939        serde(skip_serializing_if = "Vec::is_empty", default)
940    )]
941    pub inputs: Vec<CreateActionInput>,
942    #[cfg_attr(
943        feature = "network",
944        serde(skip_serializing_if = "Vec::is_empty", default)
945    )]
946    pub outputs: Vec<CreateActionOutput>,
947    #[cfg_attr(
948        feature = "network",
949        serde(default, skip_serializing_if = "Option::is_none")
950    )]
951    pub lock_time: Option<u32>,
952    #[cfg_attr(
953        feature = "network",
954        serde(default, skip_serializing_if = "Option::is_none")
955    )]
956    pub version: Option<u32>,
957    #[cfg_attr(
958        feature = "network",
959        serde(skip_serializing_if = "Vec::is_empty", default)
960    )]
961    pub labels: Vec<LabelStringUnder300Bytes>,
962    #[cfg_attr(
963        feature = "network",
964        serde(default, skip_serializing_if = "Option::is_none")
965    )]
966    pub options: Option<CreateActionOptions>,
967    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
968    pub reference: Option<String>,
969}
970
971/// Data needed to complete signing of a partial transaction.
972#[derive(Clone, Debug)]
973#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
974#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
975pub struct SignableTransaction {
976    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
977    pub tx: Vec<u8>,
978    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
979    pub reference: Vec<u8>,
980}
981
982/// Status of a transaction sent as part of a batch.
983#[derive(Clone, Debug)]
984#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
985#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
986pub struct SendWithResult {
987    pub txid: TXIDHexString,
988    pub status: ActionResultStatus,
989}
990
991/// Status of a review action result from undelayed broadcast.
992#[derive(Clone, Debug, PartialEq, Eq)]
993#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
994#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
995pub enum ReviewActionResultStatus {
996    Success,
997    DoubleSpend,
998    ServiceError,
999    InvalidTx,
1000}
1001
1002impl ReviewActionResultStatus {
1003    pub fn as_str(&self) -> &'static str {
1004        match self {
1005            ReviewActionResultStatus::Success => "success",
1006            ReviewActionResultStatus::DoubleSpend => "doubleSpend",
1007            ReviewActionResultStatus::ServiceError => "serviceError",
1008            ReviewActionResultStatus::InvalidTx => "invalidTx",
1009        }
1010    }
1011}
1012
1013/// Result of reviewing a non-delayed broadcast action.
1014#[derive(Clone, Debug)]
1015#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1016#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1017pub struct ReviewActionResult {
1018    pub txid: TXIDHexString,
1019    pub status: ReviewActionResultStatus,
1020    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
1021    pub competing_txs: Option<Vec<String>>,
1022    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
1023    pub competing_beef: Option<Vec<u8>>,
1024}
1025
1026/// Result of creating a transaction.
1027#[derive(Clone, Debug)]
1028#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1029#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1030pub struct CreateActionResult {
1031    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
1032    pub txid: Option<TXIDHexString>,
1033    #[cfg_attr(
1034        feature = "network",
1035        serde(with = "serde_helpers::option_bytes_as_array")
1036    )]
1037    #[cfg_attr(
1038        feature = "network",
1039        serde(skip_serializing_if = "Option::is_none", default)
1040    )]
1041    pub tx: Option<Vec<u8>>,
1042    #[cfg_attr(
1043        feature = "network",
1044        serde(skip_serializing_if = "Vec::is_empty", default)
1045    )]
1046    pub no_send_change: Vec<OutpointString>,
1047    #[cfg_attr(
1048        feature = "network",
1049        serde(skip_serializing_if = "Vec::is_empty", default)
1050    )]
1051    pub send_with_results: Vec<SendWithResult>,
1052    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
1053    pub signable_transaction: Option<SignableTransaction>,
1054}
1055
1056/// Unlocking script and sequence number for a specific input.
1057#[derive(Clone, Debug)]
1058#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1059#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1060pub struct SignActionSpend {
1061    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_hex"))]
1062    pub unlocking_script: Vec<u8>,
1063    #[cfg_attr(
1064        feature = "network",
1065        serde(default, skip_serializing_if = "Option::is_none")
1066    )]
1067    pub sequence_number: Option<u32>,
1068}
1069
1070/// Controls signing and broadcasting behavior.
1071#[derive(Clone, Debug, Default)]
1072#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1073#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1074pub struct SignActionOptions {
1075    #[cfg_attr(feature = "network", serde(default))]
1076    pub accept_delayed_broadcast: BooleanDefaultTrue,
1077    #[cfg_attr(feature = "network", serde(default))]
1078    pub return_txid_only: BooleanDefaultFalse,
1079    #[cfg_attr(feature = "network", serde(default))]
1080    pub no_send: BooleanDefaultFalse,
1081    #[cfg_attr(
1082        feature = "network",
1083        serde(skip_serializing_if = "Vec::is_empty", default)
1084    )]
1085    pub send_with: Vec<TXIDHexString>,
1086}
1087
1088/// Arguments for signing a previously created transaction.
1089#[derive(Clone, Debug)]
1090#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1091#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1092pub struct SignActionArgs {
1093    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1094    pub reference: Vec<u8>,
1095    pub spends: HashMap<u32, SignActionSpend>,
1096    #[cfg_attr(
1097        feature = "network",
1098        serde(default, skip_serializing_if = "Option::is_none")
1099    )]
1100    pub options: Option<SignActionOptions>,
1101}
1102
1103/// Result of a successful signing operation.
1104#[derive(Clone, Debug)]
1105#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1106#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1107pub struct SignActionResult {
1108    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
1109    pub txid: Option<TXIDHexString>,
1110    #[cfg_attr(
1111        feature = "network",
1112        serde(with = "serde_helpers::option_bytes_as_array")
1113    )]
1114    #[cfg_attr(
1115        feature = "network",
1116        serde(skip_serializing_if = "Option::is_none", default)
1117    )]
1118    pub tx: Option<Vec<u8>>,
1119    #[cfg_attr(
1120        feature = "network",
1121        serde(skip_serializing_if = "Vec::is_empty", default)
1122    )]
1123    pub send_with_results: Vec<SendWithResult>,
1124}
1125
1126/// Arguments for aborting a transaction.
1127#[derive(Clone, Debug)]
1128#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1129#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1130pub struct AbortActionArgs {
1131    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1132    pub reference: Vec<u8>,
1133}
1134
1135/// Result of aborting a transaction.
1136#[derive(Clone, Debug)]
1137#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1138#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1139pub struct AbortActionResult {
1140    pub aborted: bool,
1141}
1142
1143// ---------------------------------------------------------------------------
1144// Action detail types (for listing)
1145// ---------------------------------------------------------------------------
1146
1147/// A transaction input with full details.
1148#[derive(Clone, Debug)]
1149#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1150#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1151pub struct ActionInput {
1152    pub source_outpoint: OutpointString,
1153    pub source_satoshis: SatoshiValue,
1154    #[cfg_attr(
1155        feature = "network",
1156        serde(with = "serde_helpers::option_bytes_as_hex")
1157    )]
1158    #[cfg_attr(
1159        feature = "network",
1160        serde(skip_serializing_if = "Option::is_none", default)
1161    )]
1162    pub source_locking_script: Option<Vec<u8>>,
1163    #[cfg_attr(
1164        feature = "network",
1165        serde(with = "serde_helpers::option_bytes_as_hex")
1166    )]
1167    #[cfg_attr(
1168        feature = "network",
1169        serde(skip_serializing_if = "Option::is_none", default)
1170    )]
1171    pub unlocking_script: Option<Vec<u8>>,
1172    pub input_description: String,
1173    pub sequence_number: u32,
1174}
1175
1176/// A transaction output with full details.
1177#[derive(Clone, Debug)]
1178#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1179#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1180pub struct ActionOutput {
1181    pub satoshis: SatoshiValue,
1182    #[cfg_attr(
1183        feature = "network",
1184        serde(with = "serde_helpers::option_bytes_as_hex")
1185    )]
1186    #[cfg_attr(
1187        feature = "network",
1188        serde(skip_serializing_if = "Option::is_none", default)
1189    )]
1190    pub locking_script: Option<Vec<u8>>,
1191    pub spendable: bool,
1192    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
1193    pub custom_instructions: Option<String>,
1194    pub tags: Vec<String>,
1195    pub output_index: u32,
1196    pub output_description: String,
1197    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
1198    pub basket: Option<String>,
1199}
1200
1201/// Full details about a wallet transaction.
1202#[derive(Clone, Debug)]
1203#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1204#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1205pub struct Action {
1206    pub txid: TXIDHexString,
1207    pub satoshis: i64,
1208    pub status: ActionStatus,
1209    pub is_outgoing: bool,
1210    pub description: String,
1211    #[cfg_attr(
1212        feature = "network",
1213        serde(skip_serializing_if = "Vec::is_empty", default)
1214    )]
1215    pub labels: Vec<String>,
1216    pub version: u32,
1217    pub lock_time: u32,
1218    #[cfg_attr(
1219        feature = "network",
1220        serde(skip_serializing_if = "Vec::is_empty", default)
1221    )]
1222    pub inputs: Vec<ActionInput>,
1223    #[cfg_attr(
1224        feature = "network",
1225        serde(skip_serializing_if = "Vec::is_empty", default)
1226    )]
1227    pub outputs: Vec<ActionOutput>,
1228}
1229
1230/// Maximum number of actions or outputs that can be returned.
1231pub const MAX_ACTIONS_LIMIT: u32 = 10000;
1232
1233// ---------------------------------------------------------------------------
1234// ListActions
1235// ---------------------------------------------------------------------------
1236
1237/// Filtering and pagination options for listing wallet transactions.
1238#[derive(Clone, Debug)]
1239#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1240#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1241pub struct ListActionsArgs {
1242    #[cfg_attr(
1243        feature = "network",
1244        serde(skip_serializing_if = "Vec::is_empty", default)
1245    )]
1246    pub labels: Vec<LabelStringUnder300Bytes>,
1247    #[cfg_attr(
1248        feature = "network",
1249        serde(default, skip_serializing_if = "Option::is_none")
1250    )]
1251    pub label_query_mode: Option<QueryMode>,
1252    #[cfg_attr(feature = "network", serde(default))]
1253    pub include_labels: BooleanDefaultFalse,
1254    #[cfg_attr(feature = "network", serde(default))]
1255    pub include_inputs: BooleanDefaultFalse,
1256    #[cfg_attr(feature = "network", serde(default))]
1257    pub include_input_source_locking_scripts: BooleanDefaultFalse,
1258    #[cfg_attr(feature = "network", serde(default))]
1259    pub include_input_unlocking_scripts: BooleanDefaultFalse,
1260    #[cfg_attr(feature = "network", serde(default))]
1261    pub include_outputs: BooleanDefaultFalse,
1262    #[cfg_attr(feature = "network", serde(default))]
1263    pub include_output_locking_scripts: BooleanDefaultFalse,
1264    #[cfg_attr(feature = "network", serde(default))]
1265    pub limit: PositiveIntegerDefault10Max10000,
1266    #[cfg_attr(
1267        feature = "network",
1268        serde(default, skip_serializing_if = "Option::is_none")
1269    )]
1270    pub offset: Option<PositiveIntegerOrZero>,
1271    #[cfg_attr(feature = "network", serde(default))]
1272    pub seek_permission: BooleanDefaultTrue,
1273}
1274
1275/// Paginated list of wallet transactions.
1276#[derive(Clone, Debug)]
1277#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1278#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1279pub struct ListActionsResult {
1280    pub total_actions: u32,
1281    pub actions: Vec<Action>,
1282}
1283
1284// ---------------------------------------------------------------------------
1285// InternalizeAction
1286// ---------------------------------------------------------------------------
1287
1288/// Derivation and identity data for wallet payment outputs.
1289#[derive(Clone, Debug)]
1290#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1291#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1292pub struct Payment {
1293    // bytes_as_base64: TS SDK types these as Base64String, Go uses default
1294    // json.Marshal for []byte which produces base64. BSV Desktop expects strings.
1295    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_base64"))]
1296    pub derivation_prefix: Vec<u8>,
1297    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_base64"))]
1298    pub derivation_suffix: Vec<u8>,
1299    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
1300    pub sender_identity_key: PublicKey,
1301}
1302
1303/// Metadata for outputs being inserted into baskets.
1304#[derive(Clone, Debug)]
1305#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1306#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1307pub struct BasketInsertion {
1308    pub basket: BasketStringUnder300Bytes,
1309    #[cfg_attr(
1310        feature = "network",
1311        serde(default, skip_serializing_if = "Option::is_none")
1312    )]
1313    pub custom_instructions: Option<String>,
1314    #[cfg_attr(
1315        feature = "network",
1316        serde(skip_serializing_if = "Vec::is_empty", default)
1317    )]
1318    pub tags: Vec<OutputTagStringUnder300Bytes>,
1319}
1320
1321/// How to process a transaction output -- as payment or basket insertion.
1322///
1323/// An enum with two variants, encoding the protocol in the variant itself.
1324/// This makes impossible states unrepresentable: a WalletPayment always has
1325/// a Payment, and a BasketInsertion always has a BasketInsertion.
1326#[derive(Clone, Debug)]
1327#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1328#[cfg_attr(feature = "network", serde(tag = "protocol", rename_all = "camelCase"))]
1329pub enum InternalizeOutput {
1330    #[cfg_attr(feature = "network", serde(rename = "wallet payment"))]
1331    WalletPayment {
1332        #[cfg_attr(feature = "network", serde(rename = "outputIndex"))]
1333        output_index: u32,
1334        #[cfg_attr(feature = "network", serde(rename = "paymentRemittance"))]
1335        payment: Payment,
1336    },
1337    #[cfg_attr(feature = "network", serde(rename = "basket insertion"))]
1338    BasketInsertion {
1339        #[cfg_attr(feature = "network", serde(rename = "outputIndex"))]
1340        output_index: u32,
1341        #[cfg_attr(feature = "network", serde(rename = "insertionRemittance"))]
1342        insertion: BasketInsertion,
1343    },
1344}
1345
1346/// Arguments for importing an external transaction into the wallet.
1347#[derive(Clone, Debug)]
1348#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1349#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1350pub struct InternalizeActionArgs {
1351    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1352    pub tx: Vec<u8>,
1353    pub description: String,
1354    #[cfg_attr(
1355        feature = "network",
1356        serde(skip_serializing_if = "Vec::is_empty", default)
1357    )]
1358    pub labels: Vec<LabelStringUnder300Bytes>,
1359    #[cfg_attr(feature = "network", serde(default))]
1360    pub seek_permission: BooleanDefaultTrue,
1361    pub outputs: Vec<InternalizeOutput>,
1362}
1363
1364/// Result of internalizing a transaction.
1365#[derive(Clone, Debug)]
1366#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1367#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1368pub struct InternalizeActionResult {
1369    pub accepted: bool,
1370}
1371
1372// ---------------------------------------------------------------------------
1373// ListOutputs
1374// ---------------------------------------------------------------------------
1375
1376/// Filtering and options for listing wallet outputs.
1377#[derive(Clone, Debug)]
1378#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1379#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1380pub struct ListOutputsArgs {
1381    pub basket: BasketStringUnder300Bytes,
1382    #[cfg_attr(
1383        feature = "network",
1384        serde(skip_serializing_if = "Vec::is_empty", default)
1385    )]
1386    pub tags: Vec<OutputTagStringUnder300Bytes>,
1387    #[cfg_attr(
1388        feature = "network",
1389        serde(default, skip_serializing_if = "Option::is_none")
1390    )]
1391    pub tag_query_mode: Option<QueryMode>,
1392    #[cfg_attr(
1393        feature = "network",
1394        serde(default, skip_serializing_if = "Option::is_none")
1395    )]
1396    pub include: Option<OutputInclude>,
1397    #[cfg_attr(
1398        feature = "network",
1399        serde(skip_serializing_if = "BooleanDefaultFalse::is_none", default)
1400    )]
1401    pub include_custom_instructions: BooleanDefaultFalse,
1402    #[cfg_attr(
1403        feature = "network",
1404        serde(skip_serializing_if = "BooleanDefaultFalse::is_none", default)
1405    )]
1406    pub include_tags: BooleanDefaultFalse,
1407    #[cfg_attr(
1408        feature = "network",
1409        serde(skip_serializing_if = "BooleanDefaultFalse::is_none", default)
1410    )]
1411    pub include_labels: BooleanDefaultFalse,
1412    #[cfg_attr(
1413        feature = "network",
1414        serde(skip_serializing_if = "Option::is_none", default)
1415    )]
1416    pub limit: PositiveIntegerDefault10Max10000,
1417    #[cfg_attr(
1418        feature = "network",
1419        serde(default, skip_serializing_if = "Option::is_none")
1420    )]
1421    pub offset: Option<PositiveIntegerOrZero>,
1422    #[cfg_attr(
1423        feature = "network",
1424        serde(skip_serializing_if = "BooleanDefaultTrue::is_none", default)
1425    )]
1426    pub seek_permission: BooleanDefaultTrue,
1427}
1428
1429/// A wallet UTXO with its metadata.
1430#[derive(Clone, Debug)]
1431#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1432#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1433pub struct Output {
1434    pub satoshis: SatoshiValue,
1435    #[cfg_attr(
1436        feature = "network",
1437        serde(with = "serde_helpers::option_bytes_as_hex")
1438    )]
1439    #[cfg_attr(
1440        feature = "network",
1441        serde(skip_serializing_if = "Option::is_none", default)
1442    )]
1443    pub locking_script: Option<Vec<u8>>,
1444    pub spendable: bool,
1445    #[cfg_attr(
1446        feature = "network",
1447        serde(default, skip_serializing_if = "Option::is_none")
1448    )]
1449    pub custom_instructions: Option<String>,
1450    #[cfg_attr(
1451        feature = "network",
1452        serde(skip_serializing_if = "Vec::is_empty", default)
1453    )]
1454    pub tags: Vec<String>,
1455    pub outpoint: OutpointString,
1456    #[cfg_attr(
1457        feature = "network",
1458        serde(skip_serializing_if = "Vec::is_empty", default)
1459    )]
1460    pub labels: Vec<String>,
1461}
1462
1463/// Paginated list of wallet outputs.
1464#[derive(Clone, Debug)]
1465#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1466#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1467pub struct ListOutputsResult {
1468    pub total_outputs: u32,
1469    #[cfg_attr(
1470        feature = "network",
1471        serde(with = "serde_helpers::option_bytes_as_array")
1472    )]
1473    #[cfg_attr(
1474        feature = "network",
1475        serde(skip_serializing_if = "Option::is_none", default)
1476    )]
1477    #[cfg_attr(feature = "network", serde(rename = "BEEF"))]
1478    pub beef: Option<Vec<u8>>,
1479    pub outputs: Vec<Output>,
1480}
1481
1482// ---------------------------------------------------------------------------
1483// RelinquishOutput
1484// ---------------------------------------------------------------------------
1485
1486/// Arguments for relinquishing ownership of an output.
1487#[derive(Clone, Debug)]
1488#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1489#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1490pub struct RelinquishOutputArgs {
1491    pub basket: BasketStringUnder300Bytes,
1492    pub output: OutpointString,
1493}
1494
1495/// Result of relinquishing an output.
1496#[derive(Clone, Debug)]
1497#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1498#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1499pub struct RelinquishOutputResult {
1500    pub relinquished: bool,
1501}
1502
1503// ---------------------------------------------------------------------------
1504// Key/Crypto types
1505// ---------------------------------------------------------------------------
1506
1507/// Arguments for getting a public key.
1508#[derive(Clone, Debug)]
1509#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1510#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1511pub struct GetPublicKeyArgs {
1512    #[cfg_attr(feature = "network", serde(default))]
1513    pub identity_key: bool,
1514    #[cfg_attr(feature = "network", serde(rename = "protocolID"))]
1515    #[cfg_attr(
1516        feature = "network",
1517        serde(default, skip_serializing_if = "Option::is_none")
1518    )]
1519    pub protocol_id: Option<Protocol>,
1520    #[cfg_attr(feature = "network", serde(rename = "keyID"))]
1521    #[cfg_attr(
1522        feature = "network",
1523        serde(default, skip_serializing_if = "Option::is_none")
1524    )]
1525    pub key_id: Option<String>,
1526    #[cfg_attr(
1527        feature = "network",
1528        serde(skip_serializing_if = "Option::is_none", default)
1529    )]
1530    pub counterparty: Option<Counterparty>,
1531    #[cfg_attr(feature = "network", serde(default))]
1532    pub privileged: bool,
1533    #[cfg_attr(
1534        feature = "network",
1535        serde(default, skip_serializing_if = "Option::is_none")
1536    )]
1537    pub privileged_reason: Option<String>,
1538    #[cfg_attr(
1539        feature = "network",
1540        serde(skip_serializing_if = "Option::is_none", default)
1541    )]
1542    pub for_self: Option<bool>,
1543    #[cfg_attr(
1544        feature = "network",
1545        serde(skip_serializing_if = "Option::is_none", default)
1546    )]
1547    pub seek_permission: Option<bool>,
1548}
1549
1550/// Result of getting a public key.
1551#[derive(Clone, Debug)]
1552#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1553#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1554pub struct GetPublicKeyResult {
1555    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
1556    pub public_key: PublicKey,
1557}
1558
1559/// Arguments for encryption.
1560#[derive(Clone, Debug)]
1561#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1562#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1563pub struct EncryptArgs {
1564    #[cfg_attr(feature = "network", serde(rename = "protocolID"))]
1565    pub protocol_id: Protocol,
1566    #[cfg_attr(feature = "network", serde(rename = "keyID"))]
1567    pub key_id: String,
1568    #[cfg_attr(feature = "network", serde(default))]
1569    pub counterparty: Counterparty,
1570    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1571    pub plaintext: Vec<u8>,
1572    #[cfg_attr(feature = "network", serde(default))]
1573    pub privileged: bool,
1574    #[cfg_attr(
1575        feature = "network",
1576        serde(default, skip_serializing_if = "Option::is_none")
1577    )]
1578    pub privileged_reason: Option<String>,
1579    #[cfg_attr(feature = "network", serde(default))]
1580    pub seek_permission: Option<bool>,
1581}
1582
1583/// Result of encryption.
1584#[derive(Clone, Debug)]
1585#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1586#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1587pub struct EncryptResult {
1588    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1589    pub ciphertext: Vec<u8>,
1590}
1591
1592/// Arguments for decryption.
1593#[derive(Clone, Debug)]
1594#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1595#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1596pub struct DecryptArgs {
1597    #[cfg_attr(feature = "network", serde(rename = "protocolID"))]
1598    pub protocol_id: Protocol,
1599    #[cfg_attr(feature = "network", serde(rename = "keyID"))]
1600    pub key_id: String,
1601    #[cfg_attr(feature = "network", serde(default))]
1602    pub counterparty: Counterparty,
1603    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1604    pub ciphertext: Vec<u8>,
1605    #[cfg_attr(feature = "network", serde(default))]
1606    pub privileged: bool,
1607    #[cfg_attr(
1608        feature = "network",
1609        serde(default, skip_serializing_if = "Option::is_none")
1610    )]
1611    pub privileged_reason: Option<String>,
1612    #[cfg_attr(feature = "network", serde(default))]
1613    pub seek_permission: Option<bool>,
1614}
1615
1616/// Result of decryption.
1617#[derive(Clone, Debug)]
1618#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1619#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1620pub struct DecryptResult {
1621    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1622    pub plaintext: Vec<u8>,
1623}
1624
1625/// Arguments for creating an HMAC.
1626#[derive(Clone, Debug)]
1627#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1628#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1629pub struct CreateHmacArgs {
1630    #[cfg_attr(feature = "network", serde(rename = "protocolID"))]
1631    pub protocol_id: Protocol,
1632    #[cfg_attr(feature = "network", serde(rename = "keyID"))]
1633    pub key_id: String,
1634    #[cfg_attr(feature = "network", serde(default))]
1635    pub counterparty: Counterparty,
1636    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1637    pub data: Vec<u8>,
1638    #[cfg_attr(feature = "network", serde(default))]
1639    pub privileged: bool,
1640    #[cfg_attr(
1641        feature = "network",
1642        serde(default, skip_serializing_if = "Option::is_none")
1643    )]
1644    pub privileged_reason: Option<String>,
1645    #[cfg_attr(feature = "network", serde(default))]
1646    pub seek_permission: Option<bool>,
1647}
1648
1649/// Result of creating an HMAC.
1650#[derive(Clone, Debug)]
1651#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1652#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1653pub struct CreateHmacResult {
1654    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1655    pub hmac: Vec<u8>,
1656}
1657
1658/// Arguments for verifying an HMAC.
1659#[derive(Clone, Debug)]
1660#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1661#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1662pub struct VerifyHmacArgs {
1663    #[cfg_attr(feature = "network", serde(rename = "protocolID"))]
1664    pub protocol_id: Protocol,
1665    #[cfg_attr(feature = "network", serde(rename = "keyID"))]
1666    pub key_id: String,
1667    #[cfg_attr(feature = "network", serde(default))]
1668    pub counterparty: Counterparty,
1669    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1670    pub data: Vec<u8>,
1671    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1672    pub hmac: Vec<u8>,
1673    #[cfg_attr(feature = "network", serde(default))]
1674    pub privileged: bool,
1675    #[cfg_attr(
1676        feature = "network",
1677        serde(default, skip_serializing_if = "Option::is_none")
1678    )]
1679    pub privileged_reason: Option<String>,
1680    #[cfg_attr(feature = "network", serde(default))]
1681    pub seek_permission: Option<bool>,
1682}
1683
1684/// Result of verifying an HMAC.
1685#[derive(Clone, Debug)]
1686#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1687#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1688pub struct VerifyHmacResult {
1689    pub valid: bool,
1690}
1691
1692/// Arguments for creating a digital signature.
1693#[derive(Clone, Debug)]
1694#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1695#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1696pub struct CreateSignatureArgs {
1697    #[cfg_attr(feature = "network", serde(rename = "protocolID"))]
1698    pub protocol_id: Protocol,
1699    #[cfg_attr(feature = "network", serde(rename = "keyID"))]
1700    pub key_id: String,
1701    #[cfg_attr(feature = "network", serde(default))]
1702    pub counterparty: Counterparty,
1703    #[cfg_attr(
1704        feature = "network",
1705        serde(with = "serde_helpers::option_bytes_as_array")
1706    )]
1707    #[cfg_attr(
1708        feature = "network",
1709        serde(skip_serializing_if = "Option::is_none", default)
1710    )]
1711    pub data: Option<Vec<u8>>,
1712    #[cfg_attr(
1713        feature = "network",
1714        serde(with = "serde_helpers::option_bytes_as_array")
1715    )]
1716    #[cfg_attr(
1717        feature = "network",
1718        serde(skip_serializing_if = "Option::is_none", default)
1719    )]
1720    pub hash_to_directly_sign: Option<Vec<u8>>,
1721    #[cfg_attr(feature = "network", serde(default))]
1722    pub privileged: bool,
1723    #[cfg_attr(
1724        feature = "network",
1725        serde(default, skip_serializing_if = "Option::is_none")
1726    )]
1727    pub privileged_reason: Option<String>,
1728    #[cfg_attr(feature = "network", serde(default))]
1729    pub seek_permission: Option<bool>,
1730}
1731
1732/// Result of creating a digital signature.
1733#[derive(Clone, Debug)]
1734#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1735#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1736pub struct CreateSignatureResult {
1737    // bytes_as_array: TS SDK returns Byte[] (number array), Go SDK uses BytesList.
1738    // NOT bytes_as_hex — BSV Desktop JSON API returns [48, 69, ...] not "3045...".
1739    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1740    pub signature: Vec<u8>,
1741}
1742
1743/// Arguments for verifying a digital signature.
1744#[derive(Clone, Debug)]
1745#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1746#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1747pub struct VerifySignatureArgs {
1748    #[cfg_attr(feature = "network", serde(rename = "protocolID"))]
1749    pub protocol_id: Protocol,
1750    #[cfg_attr(feature = "network", serde(rename = "keyID"))]
1751    pub key_id: String,
1752    #[cfg_attr(feature = "network", serde(default))]
1753    pub counterparty: Counterparty,
1754    #[cfg_attr(
1755        feature = "network",
1756        serde(with = "serde_helpers::option_bytes_as_array")
1757    )]
1758    #[cfg_attr(
1759        feature = "network",
1760        serde(skip_serializing_if = "Option::is_none", default)
1761    )]
1762    pub data: Option<Vec<u8>>,
1763    #[cfg_attr(
1764        feature = "network",
1765        serde(with = "serde_helpers::option_bytes_as_array")
1766    )]
1767    #[cfg_attr(
1768        feature = "network",
1769        serde(skip_serializing_if = "Option::is_none", default)
1770    )]
1771    pub hash_to_directly_verify: Option<Vec<u8>>,
1772    // bytes_as_array: matches TS Byte[] and Go BytesList format.
1773    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
1774    pub signature: Vec<u8>,
1775    #[cfg_attr(feature = "network", serde(default))]
1776    pub for_self: Option<bool>,
1777    #[cfg_attr(feature = "network", serde(default))]
1778    pub privileged: bool,
1779    #[cfg_attr(
1780        feature = "network",
1781        serde(default, skip_serializing_if = "Option::is_none")
1782    )]
1783    pub privileged_reason: Option<String>,
1784    #[cfg_attr(feature = "network", serde(default))]
1785    pub seek_permission: Option<bool>,
1786}
1787
1788/// Result of verifying a digital signature.
1789#[derive(Clone, Debug)]
1790#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1791#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1792pub struct VerifySignatureResult {
1793    pub valid: bool,
1794}
1795
1796// ---------------------------------------------------------------------------
1797// Certificate operations
1798// ---------------------------------------------------------------------------
1799
1800/// Arguments for acquiring a new certificate.
1801#[derive(Clone, Debug)]
1802#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1803#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1804pub struct AcquireCertificateArgs {
1805    #[cfg_attr(feature = "network", serde(rename = "type"))]
1806    pub cert_type: CertificateType,
1807    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
1808    pub certifier: PublicKey,
1809    pub acquisition_protocol: AcquisitionProtocol,
1810    #[cfg_attr(
1811        feature = "network",
1812        serde(skip_serializing_if = "HashMap::is_empty", default)
1813    )]
1814    pub fields: HashMap<String, String>,
1815    #[cfg_attr(
1816        feature = "network",
1817        serde(default, skip_serializing_if = "Option::is_none")
1818    )]
1819    pub serial_number: Option<SerialNumber>,
1820    #[cfg_attr(
1821        feature = "network",
1822        serde(default, skip_serializing_if = "Option::is_none")
1823    )]
1824    pub revocation_outpoint: Option<String>,
1825    #[cfg_attr(
1826        feature = "network",
1827        serde(with = "serde_helpers::option_bytes_as_hex")
1828    )]
1829    #[cfg_attr(
1830        feature = "network",
1831        serde(skip_serializing_if = "Option::is_none", default)
1832    )]
1833    pub signature: Option<Vec<u8>>,
1834    #[cfg_attr(
1835        feature = "network",
1836        serde(default, skip_serializing_if = "Option::is_none")
1837    )]
1838    pub certifier_url: Option<String>,
1839    #[cfg_attr(
1840        feature = "network",
1841        serde(default, skip_serializing_if = "Option::is_none")
1842    )]
1843    pub keyring_revealer: Option<KeyringRevealer>,
1844    #[cfg_attr(
1845        feature = "network",
1846        serde(default, skip_serializing_if = "Option::is_none")
1847    )]
1848    pub keyring_for_subject: Option<HashMap<String, String>>,
1849    #[cfg_attr(feature = "network", serde(default))]
1850    pub privileged: bool,
1851    #[cfg_attr(
1852        feature = "network",
1853        serde(default, skip_serializing_if = "Option::is_none")
1854    )]
1855    pub privileged_reason: Option<String>,
1856}
1857
1858/// Arguments for listing certificates.
1859#[derive(Clone, Debug)]
1860#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1861#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1862pub struct ListCertificatesArgs {
1863    #[cfg_attr(
1864        feature = "network",
1865        serde(with = "serde_helpers::vec_public_key_hex", default)
1866    )]
1867    pub certifiers: Vec<PublicKey>,
1868    #[cfg_attr(
1869        feature = "network",
1870        serde(skip_serializing_if = "Vec::is_empty", default)
1871    )]
1872    pub types: Vec<CertificateType>,
1873    #[cfg_attr(feature = "network", serde(default))]
1874    pub limit: PositiveIntegerDefault10Max10000,
1875    #[cfg_attr(
1876        feature = "network",
1877        serde(default, skip_serializing_if = "Option::is_none")
1878    )]
1879    pub offset: Option<PositiveIntegerOrZero>,
1880    #[cfg_attr(feature = "network", serde(default))]
1881    pub privileged: BooleanDefaultFalse,
1882    #[cfg_attr(
1883        feature = "network",
1884        serde(default, skip_serializing_if = "Option::is_none")
1885    )]
1886    pub privileged_reason: Option<String>,
1887    /// Optional partial certificate filter for exact matching.
1888    /// When provided, only certificates matching these fields are returned.
1889    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
1890    pub partial: Option<PartialCertificate>,
1891}
1892
1893/// A certificate with its keyring and verifier.
1894#[derive(Clone, Debug)]
1895#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1896#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1897pub struct CertificateResult {
1898    #[cfg_attr(feature = "network", serde(flatten))]
1899    pub certificate: Certificate,
1900    #[cfg_attr(
1901        feature = "network",
1902        serde(default, skip_serializing_if = "Option::is_none")
1903    )]
1904    pub keyring: Option<HashMap<String, String>>,
1905    #[cfg_attr(
1906        feature = "network",
1907        serde(with = "serde_helpers::option_bytes_as_hex")
1908    )]
1909    #[cfg_attr(
1910        feature = "network",
1911        serde(skip_serializing_if = "Option::is_none", default)
1912    )]
1913    pub verifier: Option<Vec<u8>>,
1914}
1915
1916/// Paginated list of certificates.
1917#[derive(Clone, Debug)]
1918#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1919#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1920pub struct ListCertificatesResult {
1921    pub total_certificates: u32,
1922    pub certificates: Vec<CertificateResult>,
1923}
1924
1925/// Arguments for creating a verifiable certificate.
1926#[derive(Clone, Debug)]
1927#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1928#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1929pub struct ProveCertificateArgs {
1930    pub certificate: PartialCertificate,
1931    pub fields_to_reveal: Vec<String>,
1932    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
1933    pub verifier: PublicKey,
1934    #[cfg_attr(feature = "network", serde(default))]
1935    pub privileged: BooleanDefaultFalse,
1936    #[cfg_attr(
1937        feature = "network",
1938        serde(default, skip_serializing_if = "Option::is_none")
1939    )]
1940    pub privileged_reason: Option<String>,
1941}
1942
1943/// Result of creating a verifiable certificate.
1944#[derive(Clone, Debug)]
1945#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1946#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1947pub struct ProveCertificateResult {
1948    pub keyring_for_verifier: HashMap<String, String>,
1949    #[cfg_attr(
1950        feature = "network",
1951        serde(default, skip_serializing_if = "Option::is_none")
1952    )]
1953    pub certificate: Option<Certificate>,
1954    #[cfg_attr(
1955        feature = "network",
1956        serde(with = "serde_helpers::option_public_key_hex")
1957    )]
1958    #[cfg_attr(feature = "network", serde(default))]
1959    #[cfg_attr(feature = "network", serde(skip_serializing_if = "Option::is_none"))]
1960    pub verifier: Option<PublicKey>,
1961}
1962
1963/// Arguments for relinquishing ownership of a certificate.
1964#[derive(Clone, Debug)]
1965#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1966#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1967pub struct RelinquishCertificateArgs {
1968    #[cfg_attr(feature = "network", serde(rename = "type"))]
1969    pub cert_type: CertificateType,
1970    pub serial_number: SerialNumber,
1971    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
1972    pub certifier: PublicKey,
1973}
1974
1975/// Result of relinquishing a certificate.
1976#[derive(Clone, Debug)]
1977#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1978#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1979pub struct RelinquishCertificateResult {
1980    pub relinquished: bool,
1981}
1982
1983// ---------------------------------------------------------------------------
1984// Discovery types
1985// ---------------------------------------------------------------------------
1986
1987/// Information about an entity that issues identity certificates.
1988#[derive(Clone, Debug)]
1989#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
1990#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
1991pub struct IdentityCertifier {
1992    pub name: String,
1993    pub icon_url: String,
1994    pub description: String,
1995    pub trust: u8,
1996}
1997
1998/// An identity certificate with decoded fields and certifier info.
1999#[derive(Clone, Debug)]
2000#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2001#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2002pub struct IdentityCertificate {
2003    #[cfg_attr(feature = "network", serde(flatten))]
2004    pub certificate: Certificate,
2005    pub certifier_info: IdentityCertifier,
2006    pub publicly_revealed_keyring: HashMap<String, String>,
2007    pub decrypted_fields: HashMap<String, String>,
2008}
2009
2010/// Arguments for discovering certificates by identity key.
2011#[derive(Clone, Debug)]
2012#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2013#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2014pub struct DiscoverByIdentityKeyArgs {
2015    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
2016    pub identity_key: PublicKey,
2017    #[cfg_attr(
2018        feature = "network",
2019        serde(default, skip_serializing_if = "Option::is_none")
2020    )]
2021    pub limit: Option<u32>,
2022    #[cfg_attr(
2023        feature = "network",
2024        serde(default, skip_serializing_if = "Option::is_none")
2025    )]
2026    pub offset: Option<u32>,
2027    #[cfg_attr(feature = "network", serde(default))]
2028    pub seek_permission: Option<bool>,
2029}
2030
2031/// Arguments for discovering certificates by attributes.
2032#[derive(Clone, Debug)]
2033#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2034#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2035pub struct DiscoverByAttributesArgs {
2036    pub attributes: HashMap<String, String>,
2037    #[cfg_attr(
2038        feature = "network",
2039        serde(default, skip_serializing_if = "Option::is_none")
2040    )]
2041    pub limit: Option<u32>,
2042    #[cfg_attr(
2043        feature = "network",
2044        serde(default, skip_serializing_if = "Option::is_none")
2045    )]
2046    pub offset: Option<u32>,
2047    #[cfg_attr(feature = "network", serde(default))]
2048    pub seek_permission: Option<bool>,
2049}
2050
2051/// Paginated list of identity certificates found during discovery.
2052#[derive(Clone, Debug)]
2053#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2054#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2055pub struct DiscoverCertificatesResult {
2056    pub total_certificates: u32,
2057    pub certificates: Vec<IdentityCertificate>,
2058}
2059
2060// ---------------------------------------------------------------------------
2061// Key linkage types
2062// ---------------------------------------------------------------------------
2063
2064/// Arguments for revealing key linkage between counterparties.
2065#[derive(Clone, Debug)]
2066#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2067#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2068pub struct RevealCounterpartyKeyLinkageArgs {
2069    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
2070    pub counterparty: PublicKey,
2071    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
2072    pub verifier: PublicKey,
2073    #[cfg_attr(
2074        feature = "network",
2075        serde(default, skip_serializing_if = "Option::is_none")
2076    )]
2077    pub privileged: Option<bool>,
2078    #[cfg_attr(
2079        feature = "network",
2080        serde(default, skip_serializing_if = "Option::is_none")
2081    )]
2082    pub privileged_reason: Option<String>,
2083}
2084
2085/// Result of revealing counterparty key linkage.
2086#[derive(Clone, Debug)]
2087#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2088#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2089pub struct RevealCounterpartyKeyLinkageResult {
2090    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
2091    pub prover: PublicKey,
2092    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
2093    pub counterparty: PublicKey,
2094    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
2095    pub verifier: PublicKey,
2096    pub revelation_time: String,
2097    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
2098    pub encrypted_linkage: Vec<u8>,
2099    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
2100    pub encrypted_linkage_proof: Vec<u8>,
2101}
2102
2103/// Arguments for revealing specific key linkage.
2104#[derive(Clone, Debug)]
2105#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2106#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2107pub struct RevealSpecificKeyLinkageArgs {
2108    pub counterparty: Counterparty,
2109    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
2110    pub verifier: PublicKey,
2111    #[cfg_attr(feature = "network", serde(rename = "protocolID"))]
2112    pub protocol_id: Protocol,
2113    #[cfg_attr(feature = "network", serde(rename = "keyID"))]
2114    pub key_id: String,
2115    #[cfg_attr(
2116        feature = "network",
2117        serde(default, skip_serializing_if = "Option::is_none")
2118    )]
2119    pub privileged: Option<bool>,
2120    #[cfg_attr(
2121        feature = "network",
2122        serde(default, skip_serializing_if = "Option::is_none")
2123    )]
2124    pub privileged_reason: Option<String>,
2125}
2126
2127/// Result of revealing specific key linkage.
2128#[derive(Clone, Debug)]
2129#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2130#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2131pub struct RevealSpecificKeyLinkageResult {
2132    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
2133    pub encrypted_linkage: Vec<u8>,
2134    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_array"))]
2135    pub encrypted_linkage_proof: Vec<u8>,
2136    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
2137    pub prover: PublicKey,
2138    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
2139    pub verifier: PublicKey,
2140    #[cfg_attr(feature = "network", serde(with = "serde_helpers::public_key_hex"))]
2141    pub counterparty: PublicKey,
2142    #[cfg_attr(feature = "network", serde(rename = "protocolID"))]
2143    pub protocol_id: Protocol,
2144    #[cfg_attr(feature = "network", serde(rename = "keyID"))]
2145    pub key_id: String,
2146    pub proof_type: u8,
2147}
2148
2149// ---------------------------------------------------------------------------
2150// Auth/Info types
2151// ---------------------------------------------------------------------------
2152
2153/// Whether the current session is authenticated.
2154#[derive(Clone, Debug)]
2155#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2156#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2157pub struct AuthenticatedResult {
2158    pub authenticated: bool,
2159}
2160
2161/// Current blockchain height.
2162#[derive(Clone, Debug)]
2163#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2164#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2165pub struct GetHeightResult {
2166    pub height: u32,
2167}
2168
2169/// Arguments for retrieving a blockchain header at a specific height.
2170#[derive(Clone, Debug)]
2171#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2172#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2173pub struct GetHeaderArgs {
2174    pub height: u32,
2175}
2176
2177/// Blockchain header data for the requested height.
2178#[derive(Clone, Debug)]
2179#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2180#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2181pub struct GetHeaderResult {
2182    #[cfg_attr(feature = "network", serde(with = "serde_helpers::bytes_as_hex"))]
2183    pub header: Vec<u8>,
2184}
2185
2186/// Current blockchain network.
2187#[derive(Clone, Debug)]
2188#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2189#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2190pub struct GetNetworkResult {
2191    pub network: Network,
2192}
2193
2194/// Version information about the wallet implementation.
2195#[derive(Clone, Debug)]
2196#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
2197#[cfg_attr(feature = "network", serde(rename_all = "camelCase"))]
2198pub struct GetVersionResult {
2199    pub version: String,
2200}
2201
2202// ---------------------------------------------------------------------------
2203// WalletInterface trait
2204// ---------------------------------------------------------------------------
2205
2206/// The core wallet interface with all 28 async methods.
2207///
2208/// Uses `#[async_trait]` for object safety -- supports both static dispatch
2209/// (`W: WalletInterface`) and dynamic dispatch (`dyn WalletInterface`).
2210///
2211/// Every method takes `originator: Option<&str>` as the last parameter,
2212/// identifying the calling application domain.
2213#[async_trait]
2214pub trait WalletInterface: Send + Sync {
2215    // -- Action methods --
2216
2217    async fn create_action(
2218        &self,
2219        args: CreateActionArgs,
2220        originator: Option<&str>,
2221    ) -> Result<CreateActionResult, WalletError>;
2222
2223    async fn sign_action(
2224        &self,
2225        args: SignActionArgs,
2226        originator: Option<&str>,
2227    ) -> Result<SignActionResult, WalletError>;
2228
2229    async fn abort_action(
2230        &self,
2231        args: AbortActionArgs,
2232        originator: Option<&str>,
2233    ) -> Result<AbortActionResult, WalletError>;
2234
2235    async fn list_actions(
2236        &self,
2237        args: ListActionsArgs,
2238        originator: Option<&str>,
2239    ) -> Result<ListActionsResult, WalletError>;
2240
2241    async fn internalize_action(
2242        &self,
2243        args: InternalizeActionArgs,
2244        originator: Option<&str>,
2245    ) -> Result<InternalizeActionResult, WalletError>;
2246
2247    // -- Output methods --
2248
2249    async fn list_outputs(
2250        &self,
2251        args: ListOutputsArgs,
2252        originator: Option<&str>,
2253    ) -> Result<ListOutputsResult, WalletError>;
2254
2255    async fn relinquish_output(
2256        &self,
2257        args: RelinquishOutputArgs,
2258        originator: Option<&str>,
2259    ) -> Result<RelinquishOutputResult, WalletError>;
2260
2261    // -- Key/Crypto methods --
2262
2263    async fn get_public_key(
2264        &self,
2265        args: GetPublicKeyArgs,
2266        originator: Option<&str>,
2267    ) -> Result<GetPublicKeyResult, WalletError>;
2268
2269    async fn reveal_counterparty_key_linkage(
2270        &self,
2271        args: RevealCounterpartyKeyLinkageArgs,
2272        originator: Option<&str>,
2273    ) -> Result<RevealCounterpartyKeyLinkageResult, WalletError>;
2274
2275    async fn reveal_specific_key_linkage(
2276        &self,
2277        args: RevealSpecificKeyLinkageArgs,
2278        originator: Option<&str>,
2279    ) -> Result<RevealSpecificKeyLinkageResult, WalletError>;
2280
2281    async fn encrypt(
2282        &self,
2283        args: EncryptArgs,
2284        originator: Option<&str>,
2285    ) -> Result<EncryptResult, WalletError>;
2286
2287    async fn decrypt(
2288        &self,
2289        args: DecryptArgs,
2290        originator: Option<&str>,
2291    ) -> Result<DecryptResult, WalletError>;
2292
2293    async fn create_hmac(
2294        &self,
2295        args: CreateHmacArgs,
2296        originator: Option<&str>,
2297    ) -> Result<CreateHmacResult, WalletError>;
2298
2299    async fn verify_hmac(
2300        &self,
2301        args: VerifyHmacArgs,
2302        originator: Option<&str>,
2303    ) -> Result<VerifyHmacResult, WalletError>;
2304
2305    async fn create_signature(
2306        &self,
2307        args: CreateSignatureArgs,
2308        originator: Option<&str>,
2309    ) -> Result<CreateSignatureResult, WalletError>;
2310
2311    async fn verify_signature(
2312        &self,
2313        args: VerifySignatureArgs,
2314        originator: Option<&str>,
2315    ) -> Result<VerifySignatureResult, WalletError>;
2316
2317    // -- Certificate methods --
2318
2319    async fn acquire_certificate(
2320        &self,
2321        args: AcquireCertificateArgs,
2322        originator: Option<&str>,
2323    ) -> Result<Certificate, WalletError>;
2324
2325    async fn list_certificates(
2326        &self,
2327        args: ListCertificatesArgs,
2328        originator: Option<&str>,
2329    ) -> Result<ListCertificatesResult, WalletError>;
2330
2331    async fn prove_certificate(
2332        &self,
2333        args: ProveCertificateArgs,
2334        originator: Option<&str>,
2335    ) -> Result<ProveCertificateResult, WalletError>;
2336
2337    async fn relinquish_certificate(
2338        &self,
2339        args: RelinquishCertificateArgs,
2340        originator: Option<&str>,
2341    ) -> Result<RelinquishCertificateResult, WalletError>;
2342
2343    // -- Discovery methods --
2344
2345    async fn discover_by_identity_key(
2346        &self,
2347        args: DiscoverByIdentityKeyArgs,
2348        originator: Option<&str>,
2349    ) -> Result<DiscoverCertificatesResult, WalletError>;
2350
2351    async fn discover_by_attributes(
2352        &self,
2353        args: DiscoverByAttributesArgs,
2354        originator: Option<&str>,
2355    ) -> Result<DiscoverCertificatesResult, WalletError>;
2356
2357    // -- Auth/Info methods --
2358
2359    async fn is_authenticated(
2360        &self,
2361        originator: Option<&str>,
2362    ) -> Result<AuthenticatedResult, WalletError>;
2363
2364    async fn wait_for_authentication(
2365        &self,
2366        originator: Option<&str>,
2367    ) -> Result<AuthenticatedResult, WalletError>;
2368
2369    async fn get_height(&self, originator: Option<&str>) -> Result<GetHeightResult, WalletError>;
2370
2371    async fn get_header_for_height(
2372        &self,
2373        args: GetHeaderArgs,
2374        originator: Option<&str>,
2375    ) -> Result<GetHeaderResult, WalletError>;
2376
2377    async fn get_network(&self, originator: Option<&str>) -> Result<GetNetworkResult, WalletError>;
2378
2379    async fn get_version(&self, originator: Option<&str>) -> Result<GetVersionResult, WalletError>;
2380}
2381
2382#[cfg(test)]
2383mod tests {
2384    use super::*;
2385
2386    #[test]
2387    fn test_serial_number_from_string_hex_valid() {
2388        let hex = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2";
2389        let sn = SerialNumber::from_string(hex).unwrap();
2390        assert_eq!(sn.0[0], 0xa1);
2391        assert_eq!(sn.0[31], 0xb2);
2392    }
2393
2394    #[test]
2395    fn test_serial_number_from_string_base64_valid() {
2396        // 32 bytes of zeros -> base64 is "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
2397        let b64 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
2398        let sn = SerialNumber::from_string(b64).unwrap();
2399        assert_eq!(sn.0, [0u8; 32]);
2400    }
2401
2402    #[test]
2403    fn test_serial_number_from_string_base64_nonzero() {
2404        // All 0xFF bytes: base64 = "//////////////////////////////////////////8="
2405        let b64 = "//////////////////////////////////////////8=";
2406        let sn = SerialNumber::from_string(b64).unwrap();
2407        assert_eq!(sn.0, [0xffu8; 32]);
2408    }
2409
2410    #[test]
2411    fn test_serial_number_from_string_invalid_length() {
2412        assert!(SerialNumber::from_string("abc").is_err());
2413        assert!(SerialNumber::from_string("").is_err());
2414        assert!(SerialNumber::from_string("a1b2c3").is_err());
2415    }
2416
2417    #[test]
2418    fn test_serial_number_from_string_invalid_chars() {
2419        // 64 chars but not valid hex
2420        let bad_hex = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
2421        assert!(SerialNumber::from_string(bad_hex).is_err());
2422    }
2423}
2424
2425#[cfg(test)]
2426mod review_action_result_tests {
2427    use super::*;
2428
2429    #[test]
2430    fn test_review_action_result_status_values() {
2431        assert_eq!(ReviewActionResultStatus::Success.as_str(), "success");
2432        assert_eq!(
2433            ReviewActionResultStatus::DoubleSpend.as_str(),
2434            "doubleSpend"
2435        );
2436        assert_eq!(
2437            ReviewActionResultStatus::ServiceError.as_str(),
2438            "serviceError"
2439        );
2440        assert_eq!(ReviewActionResultStatus::InvalidTx.as_str(), "invalidTx");
2441    }
2442
2443    #[cfg(feature = "network")]
2444    #[test]
2445    fn test_review_action_result_serde_roundtrip() {
2446        let r = ReviewActionResult {
2447            txid: "aabb".to_string(),
2448            status: ReviewActionResultStatus::DoubleSpend,
2449            competing_txs: Some(vec!["ccdd".to_string()]),
2450            competing_beef: None,
2451        };
2452        let json = serde_json::to_string(&r).unwrap();
2453        assert!(json.contains("\"doubleSpend\""));
2454        assert!(json.contains("\"competingTxs\""));
2455        // competing_beef should be omitted since None
2456        assert!(!json.contains("\"competingBeef\""));
2457        let r2: ReviewActionResult = serde_json::from_str(&json).unwrap();
2458        assert_eq!(r2.status, ReviewActionResultStatus::DoubleSpend);
2459        assert_eq!(r2.competing_txs.unwrap()[0], "ccdd");
2460    }
2461}