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 "serde" feature)
22// ---------------------------------------------------------------------------
23
24/// Serde helpers for custom JSON serialization of wallet types.
25/// Gated behind the "serde" feature since serde is an optional dependency.
26#[cfg(feature = "serde")]
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/// Serde `skip_serializing_if` predicate that skips `false` values.
368///
369/// Used for plain `bool` fields whose TS wire form is `?: true` (the literal
370/// type `true | undefined`) — those must serialize only when `true`, and be
371/// omitted when `false` to match TS's `JSON.stringify(undefined)` behavior.
372#[cfg(feature = "serde")]
373fn is_false(b: &bool) -> bool {
374    !*b
375}
376
377// ---------------------------------------------------------------------------
378// Enums
379// ---------------------------------------------------------------------------
380
381/// Current state of a transaction action.
382#[derive(Clone, Debug, PartialEq, Eq)]
383#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
384#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
385pub enum ActionStatus {
386    Completed,
387    Unprocessed,
388    Sending,
389    Unproven,
390    Unsigned,
391    #[cfg_attr(feature = "serde", serde(rename = "nosend"))]
392    NoSend,
393    #[cfg_attr(feature = "serde", serde(rename = "nonfinal"))]
394    NonFinal,
395    Failed,
396}
397
398impl ActionStatus {
399    pub fn as_str(&self) -> &'static str {
400        match self {
401            ActionStatus::Completed => "completed",
402            ActionStatus::Unprocessed => "unprocessed",
403            ActionStatus::Sending => "sending",
404            ActionStatus::Unproven => "unproven",
405            ActionStatus::Unsigned => "unsigned",
406            ActionStatus::NoSend => "nosend",
407            ActionStatus::NonFinal => "nonfinal",
408            ActionStatus::Failed => "failed",
409        }
410    }
411}
412
413/// Status of a transaction result (subset of ActionStatus).
414#[derive(Clone, Debug, PartialEq, Eq)]
415#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
416#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
417pub enum ActionResultStatus {
418    Unproven,
419    Sending,
420    Failed,
421}
422
423impl ActionResultStatus {
424    pub fn as_str(&self) -> &'static str {
425        match self {
426            ActionResultStatus::Unproven => "unproven",
427            ActionResultStatus::Sending => "sending",
428            ActionResultStatus::Failed => "failed",
429        }
430    }
431}
432
433/// How multiple criteria are combined in queries.
434#[derive(Clone, Debug, PartialEq, Eq)]
435#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
436#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
437pub enum QueryMode {
438    Any,
439    All,
440}
441
442impl QueryMode {
443    pub fn as_str(&self) -> &'static str {
444        match self {
445            QueryMode::Any => "any",
446            QueryMode::All => "all",
447        }
448    }
449}
450
451/// What additional data to include with output listings.
452#[derive(Clone, Debug, PartialEq, Eq)]
453#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
454pub enum OutputInclude {
455    #[cfg_attr(feature = "serde", serde(rename = "locking scripts"))]
456    LockingScripts,
457    #[cfg_attr(feature = "serde", serde(rename = "entire transactions"))]
458    EntireTransactions,
459}
460
461impl OutputInclude {
462    pub fn as_str(&self) -> &'static str {
463        match self {
464            OutputInclude::LockingScripts => "locking scripts",
465            OutputInclude::EntireTransactions => "entire transactions",
466        }
467    }
468}
469
470/// Protocol for internalizing transaction outputs.
471#[derive(Clone, Debug, PartialEq, Eq)]
472#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
473pub enum InternalizeProtocol {
474    #[cfg_attr(feature = "serde", serde(rename = "wallet payment"))]
475    WalletPayment,
476    #[cfg_attr(feature = "serde", serde(rename = "basket insertion"))]
477    BasketInsertion,
478}
479
480impl InternalizeProtocol {
481    pub fn as_str(&self) -> &'static str {
482        match self {
483            InternalizeProtocol::WalletPayment => "wallet payment",
484            InternalizeProtocol::BasketInsertion => "basket insertion",
485        }
486    }
487}
488
489/// Protocol for certificate acquisition.
490#[derive(Clone, Debug, PartialEq, Eq)]
491#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
492#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
493pub enum AcquisitionProtocol {
494    Direct,
495    Issuance,
496}
497
498impl AcquisitionProtocol {
499    pub fn as_str(&self) -> &'static str {
500        match self {
501            AcquisitionProtocol::Direct => "direct",
502            AcquisitionProtocol::Issuance => "issuance",
503        }
504    }
505}
506
507/// Blockchain network type.
508#[derive(Clone, Debug, PartialEq, Eq)]
509#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
510#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
511pub enum Network {
512    Mainnet,
513    Testnet,
514}
515
516impl Network {
517    pub fn as_str(&self) -> &'static str {
518        match self {
519            Network::Mainnet => "mainnet",
520            Network::Testnet => "testnet",
521        }
522    }
523}
524
525/// Trust level for self-referential operations.
526#[derive(Clone, Debug, PartialEq, Eq)]
527#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
528#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
529pub enum TrustSelf {
530    Known,
531}
532
533impl TrustSelf {
534    pub fn as_str(&self) -> &'static str {
535        match self {
536            TrustSelf::Known => "known",
537        }
538    }
539}
540
541// ---------------------------------------------------------------------------
542// Core types: Certificate, CertificateType, SerialNumber, KeyringRevealer
543// ---------------------------------------------------------------------------
544
545/// Newtype wrapper for certificate type identifier (32 bytes).
546/// Serializes as base64 string matching Go SDK Bytes32Base64.
547#[derive(Clone, Debug, PartialEq, Eq, Hash)]
548pub struct CertificateType(pub [u8; 32]);
549
550#[cfg(feature = "serde")]
551impl serde::Serialize for CertificateType {
552    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
553        serde_helpers::bytes32_base64::serialize(&self.0, serializer)
554    }
555}
556
557#[cfg(feature = "serde")]
558impl<'de> serde::Deserialize<'de> for CertificateType {
559    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
560        serde_helpers::bytes32_base64::deserialize(deserializer).map(CertificateType)
561    }
562}
563
564impl CertificateType {
565    /// Parse a CertificateType from a base64, hex, or raw string.
566    ///
567    /// Accepts base64 (44 chars or ending with '='), hex (64 hex chars),
568    /// or raw byte strings (<=32 bytes).
569    pub fn from_string(s: &str) -> Result<Self, WalletError> {
570        let bytes = if s.len() == 44 || (!s.is_empty() && s.ends_with('=')) {
571            SerialNumber::base64_decode_sn(s)?
572        } else if s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit()) {
573            crate::primitives::utils::from_hex(s)
574                .map_err(|e| WalletError::InvalidParameter(format!("hex: {e}")))?
575        } else if s.len() <= 32 {
576            let mut buf = [0u8; 32];
577            buf[..s.len()].copy_from_slice(s.as_bytes());
578            return Ok(CertificateType(buf));
579        } else {
580            return Err(WalletError::InvalidParameter(format!(
581                "CertificateType: unsupported string length {}",
582                s.len()
583            )));
584        };
585        if bytes.len() != 32 {
586            return Err(WalletError::InvalidParameter(format!(
587                "CertificateType must decode to 32 bytes, got {}",
588                bytes.len()
589            )));
590        }
591        let mut buf = [0u8; 32];
592        buf.copy_from_slice(&bytes);
593        Ok(CertificateType(buf))
594    }
595
596    pub fn bytes(&self) -> &[u8; 32] {
597        &self.0
598    }
599}
600
601/// Newtype wrapper for certificate serial number (32 bytes).
602/// Serializes as base64 string matching Go SDK Bytes32Base64.
603#[derive(Clone, Debug, PartialEq, Eq, Hash)]
604pub struct SerialNumber(pub [u8; 32]);
605
606#[cfg(feature = "serde")]
607impl serde::Serialize for SerialNumber {
608    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
609        serde_helpers::bytes32_base64::serialize(&self.0, serializer)
610    }
611}
612
613#[cfg(feature = "serde")]
614impl<'de> serde::Deserialize<'de> for SerialNumber {
615    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
616        serde_helpers::bytes32_base64::deserialize(deserializer).map(SerialNumber)
617    }
618}
619
620impl SerialNumber {
621    /// Parse a SerialNumber from a base64 or hex string.
622    ///
623    /// Accepts:
624    /// - 44-character base64 string (with optional padding, decodes to 32 bytes)
625    /// - 64-character hex string (decodes to 32 bytes)
626    ///
627    /// Returns an error for other formats or if the decoded length is not 32 bytes.
628    pub fn from_string(s: &str) -> Result<Self, WalletError> {
629        let bytes = if s.len() == 44 || (!s.is_empty() && s.ends_with('=')) {
630            // Base64 format (32 bytes -> 44 base64 chars with padding)
631            Self::base64_decode_sn(s)?
632        } else if s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit()) {
633            // Hex format (32 bytes -> 64 hex chars)
634            crate::primitives::utils::from_hex(s)
635                .map_err(|e| WalletError::InvalidParameter(format!("hex: {e}")))?
636        } else {
637            return Err(WalletError::InvalidParameter(format!(
638                "SerialNumber string must be 44 (base64) or 64 (hex) chars, got {}",
639                s.len()
640            )));
641        };
642        if bytes.len() != 32 {
643            return Err(WalletError::InvalidParameter(
644                "SerialNumber must decode to 32 bytes".into(),
645            ));
646        }
647        let mut buf = [0u8; 32];
648        buf.copy_from_slice(&bytes);
649        Ok(SerialNumber(buf))
650    }
651
652    /// Inline base64 decoder for SerialNumber (self-contained, no cross-module dependency).
653    pub(crate) fn base64_decode_sn(s: &str) -> Result<Vec<u8>, WalletError> {
654        fn b64_val(c: u8) -> Result<u8, WalletError> {
655            match c {
656                b'A'..=b'Z' => Ok(c - b'A'),
657                b'a'..=b'z' => Ok(c - b'a' + 26),
658                b'0'..=b'9' => Ok(c - b'0' + 52),
659                b'+' => Ok(62),
660                b'/' => Ok(63),
661                _ => Err(WalletError::InvalidParameter(format!(
662                    "invalid base64 character: {}",
663                    c as char
664                ))),
665            }
666        }
667        let bytes = s.as_bytes();
668        let mut result = Vec::new();
669        let mut i = 0;
670        while i < bytes.len() {
671            if bytes[i] == b'=' {
672                break;
673            }
674            let a = b64_val(bytes[i])?;
675            let b = if i + 1 < bytes.len() && bytes[i + 1] != b'=' {
676                b64_val(bytes[i + 1])?
677            } else {
678                0
679            };
680            let c = if i + 2 < bytes.len() && bytes[i + 2] != b'=' {
681                b64_val(bytes[i + 2])?
682            } else {
683                0
684            };
685            let d = if i + 3 < bytes.len() && bytes[i + 3] != b'=' {
686                b64_val(bytes[i + 3])?
687            } else {
688                0
689            };
690            let n = (a as u32) << 18 | (b as u32) << 12 | (c as u32) << 6 | (d as u32);
691            result.push((n >> 16) as u8);
692            if i + 2 < bytes.len() && bytes[i + 2] != b'=' {
693                result.push((n >> 8) as u8);
694            }
695            if i + 3 < bytes.len() && bytes[i + 3] != b'=' {
696                result.push(n as u8);
697            }
698            i += 4;
699        }
700        Ok(result)
701    }
702
703    pub fn bytes(&self) -> &[u8; 32] {
704        &self.0
705    }
706}
707
708/// A certificate in the wallet.
709#[derive(Clone, Debug)]
710#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
711#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
712pub struct Certificate {
713    #[cfg_attr(feature = "serde", serde(rename = "type"))]
714    pub cert_type: CertificateType,
715    pub serial_number: SerialNumber,
716    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
717    pub subject: PublicKey,
718    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
719    pub certifier: PublicKey,
720    // `skip_serializing_if` without `default` is not a round-trip: serializing a
721    // `None` OMITS the key, and deserializing then rejects the very JSON we just
722    // produced ("missing field"). `signature` in particular is `None` for every
723    // UNSIGNED certificate — the state a cert is necessarily in while it is being
724    // presented for signing — so without `default` such a cert cannot be sent over
725    // the wire at all.
726    #[cfg_attr(
727        feature = "serde",
728        serde(default, skip_serializing_if = "Option::is_none")
729    )]
730    pub revocation_outpoint: Option<String>,
731    #[cfg_attr(
732        feature = "serde",
733        serde(default, skip_serializing_if = "Option::is_none")
734    )]
735    pub fields: Option<HashMap<String, String>>,
736    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::option_bytes_as_hex"))]
737    #[cfg_attr(
738        feature = "serde",
739        serde(default, skip_serializing_if = "Option::is_none")
740    )]
741    pub signature: Option<Vec<u8>>,
742}
743
744/// A partial certificate where all fields are optional.
745/// Used for ProveCertificateArgs to match TS SDK's `Partial<WalletCertificate>`.
746#[derive(Clone, Debug)]
747#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
748#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
749pub struct PartialCertificate {
750    #[cfg_attr(feature = "serde", serde(rename = "type"))]
751    #[cfg_attr(
752        feature = "serde",
753        serde(default, skip_serializing_if = "Option::is_none")
754    )]
755    pub cert_type: Option<CertificateType>,
756    #[cfg_attr(
757        feature = "serde",
758        serde(default, skip_serializing_if = "Option::is_none")
759    )]
760    pub serial_number: Option<SerialNumber>,
761    #[cfg_attr(
762        feature = "serde",
763        serde(with = "serde_helpers::option_public_key_hex")
764    )]
765    #[cfg_attr(feature = "serde", serde(default))]
766    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
767    pub subject: Option<PublicKey>,
768    #[cfg_attr(
769        feature = "serde",
770        serde(with = "serde_helpers::option_public_key_hex")
771    )]
772    #[cfg_attr(feature = "serde", serde(default))]
773    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
774    pub certifier: Option<PublicKey>,
775    #[cfg_attr(
776        feature = "serde",
777        serde(default, skip_serializing_if = "Option::is_none")
778    )]
779    pub revocation_outpoint: Option<String>,
780    #[cfg_attr(
781        feature = "serde",
782        serde(default, skip_serializing_if = "Option::is_none")
783    )]
784    pub fields: Option<HashMap<String, String>>,
785    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::option_bytes_as_hex"))]
786    #[cfg_attr(feature = "serde", serde(default))]
787    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
788    pub signature: Option<Vec<u8>>,
789}
790
791impl From<Certificate> for PartialCertificate {
792    fn from(c: Certificate) -> Self {
793        PartialCertificate {
794            cert_type: Some(c.cert_type),
795            serial_number: Some(c.serial_number),
796            subject: Some(c.subject),
797            certifier: Some(c.certifier),
798            revocation_outpoint: c.revocation_outpoint,
799            fields: c.fields,
800            signature: c.signature,
801        }
802    }
803}
804
805/// Identifies who reveals a keyring.
806#[derive(Clone, Debug)]
807pub enum KeyringRevealer {
808    /// The certifier reveals the keyring.
809    Certifier,
810    /// A specific public key reveals the keyring.
811    PubKey(PublicKey),
812}
813
814#[cfg(feature = "serde")]
815impl serde::Serialize for KeyringRevealer {
816    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
817        match self {
818            KeyringRevealer::Certifier => serializer.serialize_str("certifier"),
819            KeyringRevealer::PubKey(pk) => serializer.serialize_str(&pk.to_der_hex()),
820        }
821    }
822}
823
824#[cfg(feature = "serde")]
825impl<'de> serde::Deserialize<'de> for KeyringRevealer {
826    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
827        let s = String::deserialize(deserializer)?;
828        if s == "certifier" || s.is_empty() {
829            Ok(KeyringRevealer::Certifier)
830        } else {
831            PublicKey::from_string(&s)
832                .map(KeyringRevealer::PubKey)
833                .map_err(serde::de::Error::custom)
834        }
835    }
836}
837
838// ---------------------------------------------------------------------------
839// Action types (CreateAction, SignAction, AbortAction)
840// ---------------------------------------------------------------------------
841
842/// An input to be spent in a new transaction.
843#[derive(Clone, Debug)]
844#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
845#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
846pub struct CreateActionInput {
847    pub outpoint: OutpointString,
848    pub input_description: String,
849    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::option_bytes_as_hex"))]
850    #[cfg_attr(
851        feature = "serde",
852        serde(skip_serializing_if = "Option::is_none", default)
853    )]
854    pub unlocking_script: Option<Vec<u8>>,
855    #[cfg_attr(
856        feature = "serde",
857        serde(default, skip_serializing_if = "Option::is_none")
858    )]
859    pub unlocking_script_length: Option<u32>,
860    #[cfg_attr(
861        feature = "serde",
862        serde(default, skip_serializing_if = "Option::is_none")
863    )]
864    pub sequence_number: Option<u32>,
865}
866
867/// An output to be created in a new transaction.
868#[derive(Clone, Debug)]
869#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
870#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
871pub struct CreateActionOutput {
872    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::option_bytes_as_hex"))]
873    #[cfg_attr(
874        feature = "serde",
875        serde(skip_serializing_if = "Option::is_none", default)
876    )]
877    pub locking_script: Option<Vec<u8>>,
878    pub satoshis: SatoshiValue,
879    pub output_description: String,
880    #[cfg_attr(
881        feature = "serde",
882        serde(default, skip_serializing_if = "Option::is_none")
883    )]
884    pub basket: Option<BasketStringUnder300Bytes>,
885    #[cfg_attr(
886        feature = "serde",
887        serde(default, skip_serializing_if = "Option::is_none")
888    )]
889    pub custom_instructions: Option<String>,
890    #[cfg_attr(
891        feature = "serde",
892        serde(skip_serializing_if = "Vec::is_empty", default)
893    )]
894    pub tags: Vec<OutputTagStringUnder300Bytes>,
895}
896
897/// Optional parameters for creating a new transaction.
898#[derive(Clone, Debug, Default)]
899#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
900#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
901pub struct CreateActionOptions {
902    #[cfg_attr(
903        feature = "serde",
904        serde(
905            default = "BooleanDefaultTrue::none",
906            skip_serializing_if = "BooleanDefaultTrue::is_none"
907        )
908    )]
909    pub sign_and_process: BooleanDefaultTrue,
910    #[cfg_attr(
911        feature = "serde",
912        serde(
913            default = "BooleanDefaultTrue::none",
914            skip_serializing_if = "BooleanDefaultTrue::is_none"
915        )
916    )]
917    pub accept_delayed_broadcast: BooleanDefaultTrue,
918    #[cfg_attr(
919        feature = "serde",
920        serde(default, skip_serializing_if = "Option::is_none")
921    )]
922    pub trust_self: Option<TrustSelf>,
923    #[cfg_attr(
924        feature = "serde",
925        serde(skip_serializing_if = "Vec::is_empty", default)
926    )]
927    pub known_txids: Vec<TXIDHexString>,
928    #[cfg_attr(
929        feature = "serde",
930        serde(
931            default = "BooleanDefaultFalse::none",
932            skip_serializing_if = "BooleanDefaultFalse::is_none"
933        )
934    )]
935    pub return_txid_only: BooleanDefaultFalse,
936    #[cfg_attr(
937        feature = "serde",
938        serde(
939            default = "BooleanDefaultFalse::none",
940            skip_serializing_if = "BooleanDefaultFalse::is_none"
941        )
942    )]
943    pub no_send: BooleanDefaultFalse,
944    #[cfg_attr(
945        feature = "serde",
946        serde(skip_serializing_if = "Vec::is_empty", default)
947    )]
948    pub no_send_change: Vec<OutpointString>,
949    #[cfg_attr(
950        feature = "serde",
951        serde(skip_serializing_if = "Vec::is_empty", default)
952    )]
953    pub send_with: Vec<TXIDHexString>,
954    #[cfg_attr(
955        feature = "serde",
956        serde(
957            default = "BooleanDefaultTrue::none",
958            skip_serializing_if = "BooleanDefaultTrue::is_none"
959        )
960    )]
961    pub randomize_outputs: BooleanDefaultTrue,
962}
963
964/// Arguments for creating a new transaction.
965#[derive(Clone, Debug)]
966#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
967#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
968pub struct CreateActionArgs {
969    pub description: DescriptionString5to50Bytes,
970    #[cfg_attr(
971        feature = "serde",
972        serde(with = "serde_helpers::option_bytes_as_array")
973    )]
974    #[cfg_attr(
975        feature = "serde",
976        serde(skip_serializing_if = "Option::is_none", default)
977    )]
978    #[cfg_attr(feature = "serde", serde(rename = "inputBEEF"))]
979    pub input_beef: Option<Vec<u8>>,
980    #[cfg_attr(
981        feature = "serde",
982        serde(skip_serializing_if = "Vec::is_empty", default)
983    )]
984    pub inputs: Vec<CreateActionInput>,
985    #[cfg_attr(
986        feature = "serde",
987        serde(skip_serializing_if = "Vec::is_empty", default)
988    )]
989    pub outputs: Vec<CreateActionOutput>,
990    #[cfg_attr(
991        feature = "serde",
992        serde(default, skip_serializing_if = "Option::is_none")
993    )]
994    pub lock_time: Option<u32>,
995    #[cfg_attr(
996        feature = "serde",
997        serde(default, skip_serializing_if = "Option::is_none")
998    )]
999    pub version: Option<u32>,
1000    #[cfg_attr(
1001        feature = "serde",
1002        serde(skip_serializing_if = "Vec::is_empty", default)
1003    )]
1004    pub labels: Vec<LabelStringUnder300Bytes>,
1005    #[cfg_attr(
1006        feature = "serde",
1007        serde(default, skip_serializing_if = "Option::is_none")
1008    )]
1009    pub options: Option<CreateActionOptions>,
1010    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
1011    pub reference: Option<String>,
1012}
1013
1014/// Data needed to complete signing of a partial transaction.
1015#[derive(Clone, Debug)]
1016#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1017#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1018pub struct SignableTransaction {
1019    // bytes_as_array: TS types `tx` as `AtomicBEEF = Byte[]` (number array).
1020    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1021    pub tx: Vec<u8>,
1022    // bytes_as_base64: TS types `reference` as `Base64String`, so the wire
1023    // format is a base64 string (not a number array). See BRC-100.
1024    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_base64"))]
1025    pub reference: Vec<u8>,
1026}
1027
1028/// Status of a transaction sent as part of a batch.
1029#[derive(Clone, Debug)]
1030#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1031#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1032pub struct SendWithResult {
1033    pub txid: TXIDHexString,
1034    pub status: ActionResultStatus,
1035}
1036
1037/// Status of a review action result from undelayed broadcast.
1038#[derive(Clone, Debug, PartialEq, Eq)]
1039#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1040#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1041pub enum ReviewActionResultStatus {
1042    Success,
1043    DoubleSpend,
1044    ServiceError,
1045    InvalidTx,
1046}
1047
1048impl ReviewActionResultStatus {
1049    pub fn as_str(&self) -> &'static str {
1050        match self {
1051            ReviewActionResultStatus::Success => "success",
1052            ReviewActionResultStatus::DoubleSpend => "doubleSpend",
1053            ReviewActionResultStatus::ServiceError => "serviceError",
1054            ReviewActionResultStatus::InvalidTx => "invalidTx",
1055        }
1056    }
1057}
1058
1059/// Result of reviewing a non-delayed broadcast action.
1060#[derive(Clone, Debug)]
1061#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1062#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1063pub struct ReviewActionResult {
1064    pub txid: TXIDHexString,
1065    pub status: ReviewActionResultStatus,
1066    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
1067    pub competing_txs: Option<Vec<String>>,
1068    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
1069    pub competing_beef: Option<Vec<u8>>,
1070}
1071
1072/// Result of creating a transaction.
1073#[derive(Clone, Debug)]
1074#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1075#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1076pub struct CreateActionResult {
1077    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
1078    pub txid: Option<TXIDHexString>,
1079    #[cfg_attr(
1080        feature = "serde",
1081        serde(with = "serde_helpers::option_bytes_as_array")
1082    )]
1083    #[cfg_attr(
1084        feature = "serde",
1085        serde(skip_serializing_if = "Option::is_none", default)
1086    )]
1087    pub tx: Option<Vec<u8>>,
1088    #[cfg_attr(
1089        feature = "serde",
1090        serde(skip_serializing_if = "Vec::is_empty", default)
1091    )]
1092    pub no_send_change: Vec<OutpointString>,
1093    #[cfg_attr(
1094        feature = "serde",
1095        serde(skip_serializing_if = "Vec::is_empty", default)
1096    )]
1097    pub send_with_results: Vec<SendWithResult>,
1098    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
1099    pub signable_transaction: Option<SignableTransaction>,
1100}
1101
1102/// Unlocking script and sequence number for a specific input.
1103#[derive(Clone, Debug)]
1104#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1105#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1106pub struct SignActionSpend {
1107    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_hex"))]
1108    pub unlocking_script: Vec<u8>,
1109    #[cfg_attr(
1110        feature = "serde",
1111        serde(default, skip_serializing_if = "Option::is_none")
1112    )]
1113    pub sequence_number: Option<u32>,
1114}
1115
1116/// Controls signing and broadcasting behavior.
1117#[derive(Clone, Debug, Default)]
1118#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1119#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1120pub struct SignActionOptions {
1121    #[cfg_attr(
1122        feature = "serde",
1123        serde(
1124            default = "BooleanDefaultTrue::none",
1125            skip_serializing_if = "BooleanDefaultTrue::is_none"
1126        )
1127    )]
1128    pub accept_delayed_broadcast: BooleanDefaultTrue,
1129    #[cfg_attr(
1130        feature = "serde",
1131        serde(
1132            default = "BooleanDefaultFalse::none",
1133            skip_serializing_if = "BooleanDefaultFalse::is_none"
1134        )
1135    )]
1136    pub return_txid_only: BooleanDefaultFalse,
1137    #[cfg_attr(
1138        feature = "serde",
1139        serde(
1140            default = "BooleanDefaultFalse::none",
1141            skip_serializing_if = "BooleanDefaultFalse::is_none"
1142        )
1143    )]
1144    pub no_send: BooleanDefaultFalse,
1145    #[cfg_attr(
1146        feature = "serde",
1147        serde(skip_serializing_if = "Vec::is_empty", default)
1148    )]
1149    pub send_with: Vec<TXIDHexString>,
1150}
1151
1152/// Arguments for signing a previously created transaction.
1153#[derive(Clone, Debug)]
1154#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1155#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1156pub struct SignActionArgs {
1157    // bytes_as_base64: TS types `reference` as `Base64String` on the wire.
1158    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_base64"))]
1159    pub reference: Vec<u8>,
1160    pub spends: HashMap<u32, SignActionSpend>,
1161    #[cfg_attr(
1162        feature = "serde",
1163        serde(default, skip_serializing_if = "Option::is_none")
1164    )]
1165    pub options: Option<SignActionOptions>,
1166}
1167
1168/// Result of a successful signing operation.
1169#[derive(Clone, Debug)]
1170#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1171#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1172pub struct SignActionResult {
1173    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
1174    pub txid: Option<TXIDHexString>,
1175    #[cfg_attr(
1176        feature = "serde",
1177        serde(with = "serde_helpers::option_bytes_as_array")
1178    )]
1179    #[cfg_attr(
1180        feature = "serde",
1181        serde(skip_serializing_if = "Option::is_none", default)
1182    )]
1183    pub tx: Option<Vec<u8>>,
1184    #[cfg_attr(
1185        feature = "serde",
1186        serde(skip_serializing_if = "Vec::is_empty", default)
1187    )]
1188    pub send_with_results: Vec<SendWithResult>,
1189}
1190
1191/// Arguments for aborting a transaction.
1192#[derive(Clone, Debug)]
1193#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1194#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1195pub struct AbortActionArgs {
1196    // bytes_as_base64: TS types `reference` as `Base64String` on the wire.
1197    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_base64"))]
1198    pub reference: Vec<u8>,
1199}
1200
1201/// Result of aborting a transaction.
1202#[derive(Clone, Debug)]
1203#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1204#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1205pub struct AbortActionResult {
1206    pub aborted: bool,
1207}
1208
1209// ---------------------------------------------------------------------------
1210// Action detail types (for listing)
1211// ---------------------------------------------------------------------------
1212
1213/// A transaction input with full details.
1214#[derive(Clone, Debug)]
1215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1216#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1217pub struct ActionInput {
1218    pub source_outpoint: OutpointString,
1219    pub source_satoshis: SatoshiValue,
1220    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::option_bytes_as_hex"))]
1221    #[cfg_attr(
1222        feature = "serde",
1223        serde(skip_serializing_if = "Option::is_none", default)
1224    )]
1225    pub source_locking_script: Option<Vec<u8>>,
1226    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::option_bytes_as_hex"))]
1227    #[cfg_attr(
1228        feature = "serde",
1229        serde(skip_serializing_if = "Option::is_none", default)
1230    )]
1231    pub unlocking_script: Option<Vec<u8>>,
1232    pub input_description: String,
1233    pub sequence_number: u32,
1234}
1235
1236/// A transaction output with full details.
1237#[derive(Clone, Debug)]
1238#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1239#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1240pub struct ActionOutput {
1241    pub satoshis: SatoshiValue,
1242    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::option_bytes_as_hex"))]
1243    #[cfg_attr(
1244        feature = "serde",
1245        serde(skip_serializing_if = "Option::is_none", default)
1246    )]
1247    pub locking_script: Option<Vec<u8>>,
1248    pub spendable: bool,
1249    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
1250    pub custom_instructions: Option<String>,
1251    pub tags: Vec<String>,
1252    pub output_index: u32,
1253    pub output_description: String,
1254    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
1255    pub basket: Option<String>,
1256}
1257
1258/// Full details about a wallet transaction.
1259#[derive(Clone, Debug)]
1260#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1261#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1262pub struct Action {
1263    pub txid: TXIDHexString,
1264    pub satoshis: i64,
1265    pub status: ActionStatus,
1266    pub is_outgoing: bool,
1267    pub description: String,
1268    #[cfg_attr(
1269        feature = "serde",
1270        serde(skip_serializing_if = "Vec::is_empty", default)
1271    )]
1272    pub labels: Vec<String>,
1273    pub version: u32,
1274    pub lock_time: u32,
1275    #[cfg_attr(
1276        feature = "serde",
1277        serde(skip_serializing_if = "Vec::is_empty", default)
1278    )]
1279    pub inputs: Vec<ActionInput>,
1280    #[cfg_attr(
1281        feature = "serde",
1282        serde(skip_serializing_if = "Vec::is_empty", default)
1283    )]
1284    pub outputs: Vec<ActionOutput>,
1285}
1286
1287/// Maximum number of actions or outputs that can be returned.
1288pub const MAX_ACTIONS_LIMIT: u32 = 10000;
1289
1290// ---------------------------------------------------------------------------
1291// ListActions
1292// ---------------------------------------------------------------------------
1293
1294/// Filtering and pagination options for listing wallet transactions.
1295#[derive(Clone, Debug)]
1296#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1297#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1298pub struct ListActionsArgs {
1299    #[cfg_attr(
1300        feature = "serde",
1301        serde(skip_serializing_if = "Vec::is_empty", default)
1302    )]
1303    pub labels: Vec<LabelStringUnder300Bytes>,
1304    #[cfg_attr(
1305        feature = "serde",
1306        serde(default, skip_serializing_if = "Option::is_none")
1307    )]
1308    pub label_query_mode: Option<QueryMode>,
1309    #[cfg_attr(
1310        feature = "serde",
1311        serde(
1312            default = "BooleanDefaultFalse::none",
1313            skip_serializing_if = "BooleanDefaultFalse::is_none"
1314        )
1315    )]
1316    pub include_labels: BooleanDefaultFalse,
1317    #[cfg_attr(
1318        feature = "serde",
1319        serde(
1320            default = "BooleanDefaultFalse::none",
1321            skip_serializing_if = "BooleanDefaultFalse::is_none"
1322        )
1323    )]
1324    pub include_inputs: BooleanDefaultFalse,
1325    #[cfg_attr(
1326        feature = "serde",
1327        serde(
1328            default = "BooleanDefaultFalse::none",
1329            skip_serializing_if = "BooleanDefaultFalse::is_none"
1330        )
1331    )]
1332    pub include_input_source_locking_scripts: BooleanDefaultFalse,
1333    #[cfg_attr(
1334        feature = "serde",
1335        serde(
1336            default = "BooleanDefaultFalse::none",
1337            skip_serializing_if = "BooleanDefaultFalse::is_none"
1338        )
1339    )]
1340    pub include_input_unlocking_scripts: BooleanDefaultFalse,
1341    #[cfg_attr(
1342        feature = "serde",
1343        serde(
1344            default = "BooleanDefaultFalse::none",
1345            skip_serializing_if = "BooleanDefaultFalse::is_none"
1346        )
1347    )]
1348    pub include_outputs: BooleanDefaultFalse,
1349    #[cfg_attr(
1350        feature = "serde",
1351        serde(
1352            default = "BooleanDefaultFalse::none",
1353            skip_serializing_if = "BooleanDefaultFalse::is_none"
1354        )
1355    )]
1356    pub include_output_locking_scripts: BooleanDefaultFalse,
1357    #[cfg_attr(feature = "serde", serde(default))]
1358    pub limit: PositiveIntegerDefault10Max10000,
1359    #[cfg_attr(
1360        feature = "serde",
1361        serde(default, skip_serializing_if = "Option::is_none")
1362    )]
1363    pub offset: Option<PositiveIntegerOrZero>,
1364    #[cfg_attr(
1365        feature = "serde",
1366        serde(
1367            default = "BooleanDefaultTrue::none",
1368            skip_serializing_if = "BooleanDefaultTrue::is_none"
1369        )
1370    )]
1371    pub seek_permission: BooleanDefaultTrue,
1372}
1373
1374/// Paginated list of wallet transactions.
1375#[derive(Clone, Debug)]
1376#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1377#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1378pub struct ListActionsResult {
1379    pub total_actions: u32,
1380    pub actions: Vec<Action>,
1381}
1382
1383// ---------------------------------------------------------------------------
1384// InternalizeAction
1385// ---------------------------------------------------------------------------
1386
1387/// Derivation and identity data for wallet payment outputs.
1388#[derive(Clone, Debug)]
1389#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1390#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1391pub struct Payment {
1392    // bytes_as_base64: TS SDK types these as Base64String, Go uses default
1393    // json.Marshal for []byte which produces base64. BSV Desktop expects strings.
1394    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_base64"))]
1395    pub derivation_prefix: Vec<u8>,
1396    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_base64"))]
1397    pub derivation_suffix: Vec<u8>,
1398    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
1399    pub sender_identity_key: PublicKey,
1400}
1401
1402/// Metadata for outputs being inserted into baskets.
1403#[derive(Clone, Debug)]
1404#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1405#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1406pub struct BasketInsertion {
1407    pub basket: BasketStringUnder300Bytes,
1408    #[cfg_attr(
1409        feature = "serde",
1410        serde(default, skip_serializing_if = "Option::is_none")
1411    )]
1412    pub custom_instructions: Option<String>,
1413    #[cfg_attr(
1414        feature = "serde",
1415        serde(skip_serializing_if = "Vec::is_empty", default)
1416    )]
1417    pub tags: Vec<OutputTagStringUnder300Bytes>,
1418}
1419
1420/// How to process a transaction output -- as payment or basket insertion.
1421///
1422/// An enum with two variants, encoding the protocol in the variant itself.
1423/// This makes impossible states unrepresentable: a WalletPayment always has
1424/// a Payment, and a BasketInsertion always has a BasketInsertion.
1425#[derive(Clone, Debug)]
1426#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1427#[cfg_attr(feature = "serde", serde(tag = "protocol", rename_all = "camelCase"))]
1428pub enum InternalizeOutput {
1429    #[cfg_attr(feature = "serde", serde(rename = "wallet payment"))]
1430    WalletPayment {
1431        #[cfg_attr(feature = "serde", serde(rename = "outputIndex"))]
1432        output_index: u32,
1433        #[cfg_attr(feature = "serde", serde(rename = "paymentRemittance"))]
1434        payment: Payment,
1435    },
1436    #[cfg_attr(feature = "serde", serde(rename = "basket insertion"))]
1437    BasketInsertion {
1438        #[cfg_attr(feature = "serde", serde(rename = "outputIndex"))]
1439        output_index: u32,
1440        #[cfg_attr(feature = "serde", serde(rename = "insertionRemittance"))]
1441        insertion: BasketInsertion,
1442    },
1443}
1444
1445/// Arguments for importing an external transaction into the wallet.
1446#[derive(Clone, Debug)]
1447#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1448#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1449pub struct InternalizeActionArgs {
1450    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1451    pub tx: Vec<u8>,
1452    pub description: String,
1453    #[cfg_attr(
1454        feature = "serde",
1455        serde(skip_serializing_if = "Vec::is_empty", default)
1456    )]
1457    pub labels: Vec<LabelStringUnder300Bytes>,
1458    #[cfg_attr(
1459        feature = "serde",
1460        serde(
1461            default = "BooleanDefaultTrue::none",
1462            skip_serializing_if = "BooleanDefaultTrue::is_none"
1463        )
1464    )]
1465    pub seek_permission: BooleanDefaultTrue,
1466    pub outputs: Vec<InternalizeOutput>,
1467}
1468
1469/// Result of internalizing a transaction.
1470#[derive(Clone, Debug)]
1471#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1472#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1473pub struct InternalizeActionResult {
1474    pub accepted: bool,
1475}
1476
1477// ---------------------------------------------------------------------------
1478// ListOutputs
1479// ---------------------------------------------------------------------------
1480
1481/// Filtering and options for listing wallet outputs.
1482#[derive(Clone, Debug)]
1483#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1484#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1485pub struct ListOutputsArgs {
1486    pub basket: BasketStringUnder300Bytes,
1487    #[cfg_attr(
1488        feature = "serde",
1489        serde(skip_serializing_if = "Vec::is_empty", default)
1490    )]
1491    pub tags: Vec<OutputTagStringUnder300Bytes>,
1492    #[cfg_attr(
1493        feature = "serde",
1494        serde(default, skip_serializing_if = "Option::is_none")
1495    )]
1496    pub tag_query_mode: Option<QueryMode>,
1497    #[cfg_attr(
1498        feature = "serde",
1499        serde(default, skip_serializing_if = "Option::is_none")
1500    )]
1501    pub include: Option<OutputInclude>,
1502    #[cfg_attr(
1503        feature = "serde",
1504        serde(
1505            default = "BooleanDefaultFalse::none",
1506            skip_serializing_if = "BooleanDefaultFalse::is_none"
1507        )
1508    )]
1509    pub include_custom_instructions: BooleanDefaultFalse,
1510    #[cfg_attr(
1511        feature = "serde",
1512        serde(
1513            default = "BooleanDefaultFalse::none",
1514            skip_serializing_if = "BooleanDefaultFalse::is_none"
1515        )
1516    )]
1517    pub include_tags: BooleanDefaultFalse,
1518    #[cfg_attr(
1519        feature = "serde",
1520        serde(
1521            default = "BooleanDefaultFalse::none",
1522            skip_serializing_if = "BooleanDefaultFalse::is_none"
1523        )
1524    )]
1525    pub include_labels: BooleanDefaultFalse,
1526    #[cfg_attr(
1527        feature = "serde",
1528        serde(skip_serializing_if = "Option::is_none", default)
1529    )]
1530    pub limit: PositiveIntegerDefault10Max10000,
1531    #[cfg_attr(
1532        feature = "serde",
1533        serde(default, skip_serializing_if = "Option::is_none")
1534    )]
1535    pub offset: Option<PositiveIntegerOrZero>,
1536    #[cfg_attr(
1537        feature = "serde",
1538        serde(
1539            default = "BooleanDefaultTrue::none",
1540            skip_serializing_if = "BooleanDefaultTrue::is_none"
1541        )
1542    )]
1543    pub seek_permission: BooleanDefaultTrue,
1544}
1545
1546/// A wallet UTXO with its metadata.
1547#[derive(Clone, Debug)]
1548#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1549#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1550pub struct Output {
1551    pub satoshis: SatoshiValue,
1552    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::option_bytes_as_hex"))]
1553    #[cfg_attr(
1554        feature = "serde",
1555        serde(skip_serializing_if = "Option::is_none", default)
1556    )]
1557    pub locking_script: Option<Vec<u8>>,
1558    pub spendable: bool,
1559    #[cfg_attr(
1560        feature = "serde",
1561        serde(default, skip_serializing_if = "Option::is_none")
1562    )]
1563    pub custom_instructions: Option<String>,
1564    #[cfg_attr(
1565        feature = "serde",
1566        serde(skip_serializing_if = "Vec::is_empty", default)
1567    )]
1568    pub tags: Vec<String>,
1569    pub outpoint: OutpointString,
1570    #[cfg_attr(
1571        feature = "serde",
1572        serde(skip_serializing_if = "Vec::is_empty", default)
1573    )]
1574    pub labels: Vec<String>,
1575}
1576
1577/// Paginated list of wallet outputs.
1578#[derive(Clone, Debug)]
1579#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1580#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1581pub struct ListOutputsResult {
1582    pub total_outputs: u32,
1583    #[cfg_attr(
1584        feature = "serde",
1585        serde(with = "serde_helpers::option_bytes_as_array")
1586    )]
1587    #[cfg_attr(
1588        feature = "serde",
1589        serde(skip_serializing_if = "Option::is_none", default)
1590    )]
1591    #[cfg_attr(feature = "serde", serde(rename = "BEEF"))]
1592    pub beef: Option<Vec<u8>>,
1593    pub outputs: Vec<Output>,
1594}
1595
1596// ---------------------------------------------------------------------------
1597// RelinquishOutput
1598// ---------------------------------------------------------------------------
1599
1600/// Arguments for relinquishing ownership of an output.
1601#[derive(Clone, Debug)]
1602#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1603#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1604pub struct RelinquishOutputArgs {
1605    pub basket: BasketStringUnder300Bytes,
1606    pub output: OutpointString,
1607}
1608
1609/// Result of relinquishing an output.
1610#[derive(Clone, Debug)]
1611#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1612#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1613pub struct RelinquishOutputResult {
1614    pub relinquished: bool,
1615}
1616
1617// ---------------------------------------------------------------------------
1618// Key/Crypto types
1619// ---------------------------------------------------------------------------
1620
1621/// Arguments for getting a public key.
1622#[derive(Clone, Debug)]
1623#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1624#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1625pub struct GetPublicKeyArgs {
1626    // TS types this as `identityKey?: true` (literal `true | undefined`),
1627    // so the wire form is either present-and-true or absent. Skip when false
1628    // to match TS's `JSON.stringify(undefined)` omission.
1629    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
1630    pub identity_key: bool,
1631    #[cfg_attr(feature = "serde", serde(rename = "protocolID"))]
1632    #[cfg_attr(
1633        feature = "serde",
1634        serde(default, skip_serializing_if = "Option::is_none")
1635    )]
1636    pub protocol_id: Option<Protocol>,
1637    #[cfg_attr(feature = "serde", serde(rename = "keyID"))]
1638    #[cfg_attr(
1639        feature = "serde",
1640        serde(default, skip_serializing_if = "Option::is_none")
1641    )]
1642    pub key_id: Option<String>,
1643    #[cfg_attr(
1644        feature = "serde",
1645        serde(skip_serializing_if = "Option::is_none", default)
1646    )]
1647    pub counterparty: Option<Counterparty>,
1648    #[cfg_attr(feature = "serde", serde(default))]
1649    pub privileged: bool,
1650    #[cfg_attr(
1651        feature = "serde",
1652        serde(default, skip_serializing_if = "Option::is_none")
1653    )]
1654    pub privileged_reason: Option<String>,
1655    #[cfg_attr(
1656        feature = "serde",
1657        serde(skip_serializing_if = "Option::is_none", default)
1658    )]
1659    pub for_self: Option<bool>,
1660    #[cfg_attr(
1661        feature = "serde",
1662        serde(skip_serializing_if = "Option::is_none", default)
1663    )]
1664    pub seek_permission: Option<bool>,
1665}
1666
1667/// Result of getting a public key.
1668#[derive(Clone, Debug)]
1669#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1670#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1671pub struct GetPublicKeyResult {
1672    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
1673    pub public_key: PublicKey,
1674}
1675
1676/// Arguments for encryption.
1677#[derive(Clone, Debug)]
1678#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1679#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1680pub struct EncryptArgs {
1681    #[cfg_attr(feature = "serde", serde(rename = "protocolID"))]
1682    pub protocol_id: Protocol,
1683    #[cfg_attr(feature = "serde", serde(rename = "keyID"))]
1684    pub key_id: String,
1685    #[cfg_attr(feature = "serde", serde(default))]
1686    pub counterparty: Counterparty,
1687    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1688    pub plaintext: Vec<u8>,
1689    #[cfg_attr(feature = "serde", serde(default))]
1690    pub privileged: bool,
1691    #[cfg_attr(
1692        feature = "serde",
1693        serde(default, skip_serializing_if = "Option::is_none")
1694    )]
1695    pub privileged_reason: Option<String>,
1696    #[cfg_attr(
1697        feature = "serde",
1698        serde(default, skip_serializing_if = "Option::is_none")
1699    )]
1700    pub seek_permission: Option<bool>,
1701}
1702
1703/// Result of encryption.
1704#[derive(Clone, Debug)]
1705#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1706#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1707pub struct EncryptResult {
1708    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1709    pub ciphertext: Vec<u8>,
1710}
1711
1712/// Arguments for decryption.
1713#[derive(Clone, Debug)]
1714#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1715#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1716pub struct DecryptArgs {
1717    #[cfg_attr(feature = "serde", serde(rename = "protocolID"))]
1718    pub protocol_id: Protocol,
1719    #[cfg_attr(feature = "serde", serde(rename = "keyID"))]
1720    pub key_id: String,
1721    #[cfg_attr(feature = "serde", serde(default))]
1722    pub counterparty: Counterparty,
1723    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1724    pub ciphertext: Vec<u8>,
1725    #[cfg_attr(feature = "serde", serde(default))]
1726    pub privileged: bool,
1727    #[cfg_attr(
1728        feature = "serde",
1729        serde(default, skip_serializing_if = "Option::is_none")
1730    )]
1731    pub privileged_reason: Option<String>,
1732    #[cfg_attr(
1733        feature = "serde",
1734        serde(default, skip_serializing_if = "Option::is_none")
1735    )]
1736    pub seek_permission: Option<bool>,
1737}
1738
1739/// Result of decryption.
1740#[derive(Clone, Debug)]
1741#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1742#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1743pub struct DecryptResult {
1744    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1745    pub plaintext: Vec<u8>,
1746}
1747
1748/// Arguments for creating an HMAC.
1749#[derive(Clone, Debug)]
1750#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1751#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1752pub struct CreateHmacArgs {
1753    #[cfg_attr(feature = "serde", serde(rename = "protocolID"))]
1754    pub protocol_id: Protocol,
1755    #[cfg_attr(feature = "serde", serde(rename = "keyID"))]
1756    pub key_id: String,
1757    #[cfg_attr(feature = "serde", serde(default))]
1758    pub counterparty: Counterparty,
1759    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1760    pub data: Vec<u8>,
1761    #[cfg_attr(feature = "serde", serde(default))]
1762    pub privileged: bool,
1763    #[cfg_attr(
1764        feature = "serde",
1765        serde(default, skip_serializing_if = "Option::is_none")
1766    )]
1767    pub privileged_reason: Option<String>,
1768    #[cfg_attr(
1769        feature = "serde",
1770        serde(default, skip_serializing_if = "Option::is_none")
1771    )]
1772    pub seek_permission: Option<bool>,
1773}
1774
1775/// Result of creating an HMAC.
1776#[derive(Clone, Debug)]
1777#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1778#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1779pub struct CreateHmacResult {
1780    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1781    pub hmac: Vec<u8>,
1782}
1783
1784/// Arguments for verifying an HMAC.
1785#[derive(Clone, Debug)]
1786#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1787#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1788pub struct VerifyHmacArgs {
1789    #[cfg_attr(feature = "serde", serde(rename = "protocolID"))]
1790    pub protocol_id: Protocol,
1791    #[cfg_attr(feature = "serde", serde(rename = "keyID"))]
1792    pub key_id: String,
1793    #[cfg_attr(feature = "serde", serde(default))]
1794    pub counterparty: Counterparty,
1795    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1796    pub data: Vec<u8>,
1797    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1798    pub hmac: Vec<u8>,
1799    #[cfg_attr(feature = "serde", serde(default))]
1800    pub privileged: bool,
1801    #[cfg_attr(
1802        feature = "serde",
1803        serde(default, skip_serializing_if = "Option::is_none")
1804    )]
1805    pub privileged_reason: Option<String>,
1806    #[cfg_attr(
1807        feature = "serde",
1808        serde(default, skip_serializing_if = "Option::is_none")
1809    )]
1810    pub seek_permission: Option<bool>,
1811}
1812
1813/// Result of verifying an HMAC.
1814#[derive(Clone, Debug)]
1815#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1816#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1817pub struct VerifyHmacResult {
1818    pub valid: bool,
1819}
1820
1821/// Arguments for creating a digital signature.
1822#[derive(Clone, Debug)]
1823#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1824#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1825pub struct CreateSignatureArgs {
1826    #[cfg_attr(feature = "serde", serde(rename = "protocolID"))]
1827    pub protocol_id: Protocol,
1828    #[cfg_attr(feature = "serde", serde(rename = "keyID"))]
1829    pub key_id: String,
1830    #[cfg_attr(feature = "serde", serde(default))]
1831    pub counterparty: Counterparty,
1832    #[cfg_attr(
1833        feature = "serde",
1834        serde(with = "serde_helpers::option_bytes_as_array")
1835    )]
1836    #[cfg_attr(
1837        feature = "serde",
1838        serde(skip_serializing_if = "Option::is_none", default)
1839    )]
1840    pub data: Option<Vec<u8>>,
1841    #[cfg_attr(
1842        feature = "serde",
1843        serde(with = "serde_helpers::option_bytes_as_array")
1844    )]
1845    #[cfg_attr(
1846        feature = "serde",
1847        serde(skip_serializing_if = "Option::is_none", default)
1848    )]
1849    pub hash_to_directly_sign: Option<Vec<u8>>,
1850    #[cfg_attr(feature = "serde", serde(default))]
1851    pub privileged: bool,
1852    #[cfg_attr(
1853        feature = "serde",
1854        serde(default, skip_serializing_if = "Option::is_none")
1855    )]
1856    pub privileged_reason: Option<String>,
1857    #[cfg_attr(
1858        feature = "serde",
1859        serde(default, skip_serializing_if = "Option::is_none")
1860    )]
1861    pub seek_permission: Option<bool>,
1862}
1863
1864/// Result of creating a digital signature.
1865#[derive(Clone, Debug)]
1866#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1867#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1868pub struct CreateSignatureResult {
1869    // bytes_as_array: TS SDK returns Byte[] (number array), Go SDK uses BytesList.
1870    // NOT bytes_as_hex — BSV Desktop JSON API returns [48, 69, ...] not "3045...".
1871    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1872    pub signature: Vec<u8>,
1873}
1874
1875/// Arguments for verifying a digital signature.
1876#[derive(Clone, Debug)]
1877#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1878#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1879pub struct VerifySignatureArgs {
1880    #[cfg_attr(feature = "serde", serde(rename = "protocolID"))]
1881    pub protocol_id: Protocol,
1882    #[cfg_attr(feature = "serde", serde(rename = "keyID"))]
1883    pub key_id: String,
1884    #[cfg_attr(feature = "serde", serde(default))]
1885    pub counterparty: Counterparty,
1886    #[cfg_attr(
1887        feature = "serde",
1888        serde(with = "serde_helpers::option_bytes_as_array")
1889    )]
1890    #[cfg_attr(
1891        feature = "serde",
1892        serde(skip_serializing_if = "Option::is_none", default)
1893    )]
1894    pub data: Option<Vec<u8>>,
1895    #[cfg_attr(
1896        feature = "serde",
1897        serde(with = "serde_helpers::option_bytes_as_array")
1898    )]
1899    #[cfg_attr(
1900        feature = "serde",
1901        serde(skip_serializing_if = "Option::is_none", default)
1902    )]
1903    pub hash_to_directly_verify: Option<Vec<u8>>,
1904    // bytes_as_array: matches TS Byte[] and Go BytesList format.
1905    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
1906    pub signature: Vec<u8>,
1907    #[cfg_attr(
1908        feature = "serde",
1909        serde(default, skip_serializing_if = "Option::is_none")
1910    )]
1911    pub for_self: Option<bool>,
1912    #[cfg_attr(feature = "serde", serde(default))]
1913    pub privileged: bool,
1914    #[cfg_attr(
1915        feature = "serde",
1916        serde(default, skip_serializing_if = "Option::is_none")
1917    )]
1918    pub privileged_reason: Option<String>,
1919    #[cfg_attr(
1920        feature = "serde",
1921        serde(default, skip_serializing_if = "Option::is_none")
1922    )]
1923    pub seek_permission: Option<bool>,
1924}
1925
1926/// Result of verifying a digital signature.
1927#[derive(Clone, Debug)]
1928#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1929#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1930pub struct VerifySignatureResult {
1931    pub valid: bool,
1932}
1933
1934// ---------------------------------------------------------------------------
1935// Certificate operations
1936// ---------------------------------------------------------------------------
1937
1938/// Arguments for acquiring a new certificate.
1939#[derive(Clone, Debug)]
1940#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1941#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1942pub struct AcquireCertificateArgs {
1943    #[cfg_attr(feature = "serde", serde(rename = "type"))]
1944    pub cert_type: CertificateType,
1945    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
1946    pub certifier: PublicKey,
1947    pub acquisition_protocol: AcquisitionProtocol,
1948    #[cfg_attr(
1949        feature = "serde",
1950        serde(skip_serializing_if = "HashMap::is_empty", default)
1951    )]
1952    pub fields: HashMap<String, String>,
1953    #[cfg_attr(
1954        feature = "serde",
1955        serde(default, skip_serializing_if = "Option::is_none")
1956    )]
1957    pub serial_number: Option<SerialNumber>,
1958    #[cfg_attr(
1959        feature = "serde",
1960        serde(default, skip_serializing_if = "Option::is_none")
1961    )]
1962    pub revocation_outpoint: Option<String>,
1963    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::option_bytes_as_hex"))]
1964    #[cfg_attr(
1965        feature = "serde",
1966        serde(skip_serializing_if = "Option::is_none", default)
1967    )]
1968    pub signature: Option<Vec<u8>>,
1969    #[cfg_attr(
1970        feature = "serde",
1971        serde(default, skip_serializing_if = "Option::is_none")
1972    )]
1973    pub certifier_url: Option<String>,
1974    #[cfg_attr(
1975        feature = "serde",
1976        serde(default, skip_serializing_if = "Option::is_none")
1977    )]
1978    pub keyring_revealer: Option<KeyringRevealer>,
1979    #[cfg_attr(
1980        feature = "serde",
1981        serde(default, skip_serializing_if = "Option::is_none")
1982    )]
1983    pub keyring_for_subject: Option<HashMap<String, String>>,
1984    #[cfg_attr(feature = "serde", serde(default))]
1985    pub privileged: bool,
1986    #[cfg_attr(
1987        feature = "serde",
1988        serde(default, skip_serializing_if = "Option::is_none")
1989    )]
1990    pub privileged_reason: Option<String>,
1991}
1992
1993/// Arguments for listing certificates.
1994#[derive(Clone, Debug)]
1995#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1996#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1997pub struct ListCertificatesArgs {
1998    #[cfg_attr(
1999        feature = "serde",
2000        serde(with = "serde_helpers::vec_public_key_hex", default)
2001    )]
2002    pub certifiers: Vec<PublicKey>,
2003    #[cfg_attr(
2004        feature = "serde",
2005        serde(skip_serializing_if = "Vec::is_empty", default)
2006    )]
2007    pub types: Vec<CertificateType>,
2008    #[cfg_attr(feature = "serde", serde(default))]
2009    pub limit: PositiveIntegerDefault10Max10000,
2010    #[cfg_attr(
2011        feature = "serde",
2012        serde(default, skip_serializing_if = "Option::is_none")
2013    )]
2014    pub offset: Option<PositiveIntegerOrZero>,
2015    #[cfg_attr(
2016        feature = "serde",
2017        serde(
2018            default = "BooleanDefaultFalse::none",
2019            skip_serializing_if = "BooleanDefaultFalse::is_none"
2020        )
2021    )]
2022    pub privileged: BooleanDefaultFalse,
2023    #[cfg_attr(
2024        feature = "serde",
2025        serde(default, skip_serializing_if = "Option::is_none")
2026    )]
2027    pub privileged_reason: Option<String>,
2028    /// Optional partial certificate filter for exact matching.
2029    /// When provided, only certificates matching these fields are returned.
2030    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
2031    pub partial: Option<PartialCertificate>,
2032}
2033
2034/// A certificate with its keyring and verifier.
2035#[derive(Clone, Debug)]
2036#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2037#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2038pub struct CertificateResult {
2039    #[cfg_attr(feature = "serde", serde(flatten))]
2040    pub certificate: Certificate,
2041    #[cfg_attr(
2042        feature = "serde",
2043        serde(default, skip_serializing_if = "Option::is_none")
2044    )]
2045    pub keyring: Option<HashMap<String, String>>,
2046    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::option_bytes_as_hex"))]
2047    #[cfg_attr(
2048        feature = "serde",
2049        serde(skip_serializing_if = "Option::is_none", default)
2050    )]
2051    pub verifier: Option<Vec<u8>>,
2052}
2053
2054/// Paginated list of certificates.
2055#[derive(Clone, Debug)]
2056#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2057#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2058pub struct ListCertificatesResult {
2059    pub total_certificates: u32,
2060    pub certificates: Vec<CertificateResult>,
2061}
2062
2063/// Arguments for creating a verifiable certificate.
2064#[derive(Clone, Debug)]
2065#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2066#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2067pub struct ProveCertificateArgs {
2068    pub certificate: PartialCertificate,
2069    pub fields_to_reveal: Vec<String>,
2070    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2071    pub verifier: PublicKey,
2072    #[cfg_attr(
2073        feature = "serde",
2074        serde(
2075            default = "BooleanDefaultFalse::none",
2076            skip_serializing_if = "BooleanDefaultFalse::is_none"
2077        )
2078    )]
2079    pub privileged: BooleanDefaultFalse,
2080    #[cfg_attr(
2081        feature = "serde",
2082        serde(default, skip_serializing_if = "Option::is_none")
2083    )]
2084    pub privileged_reason: Option<String>,
2085}
2086
2087/// Result of creating a verifiable certificate.
2088#[derive(Clone, Debug)]
2089#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2090#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2091pub struct ProveCertificateResult {
2092    pub keyring_for_verifier: HashMap<String, String>,
2093    #[cfg_attr(
2094        feature = "serde",
2095        serde(default, skip_serializing_if = "Option::is_none")
2096    )]
2097    pub certificate: Option<Certificate>,
2098    #[cfg_attr(
2099        feature = "serde",
2100        serde(with = "serde_helpers::option_public_key_hex")
2101    )]
2102    #[cfg_attr(feature = "serde", serde(default))]
2103    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
2104    pub verifier: Option<PublicKey>,
2105}
2106
2107/// Arguments for relinquishing ownership of a certificate.
2108#[derive(Clone, Debug)]
2109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2110#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2111pub struct RelinquishCertificateArgs {
2112    #[cfg_attr(feature = "serde", serde(rename = "type"))]
2113    pub cert_type: CertificateType,
2114    pub serial_number: SerialNumber,
2115    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2116    pub certifier: PublicKey,
2117}
2118
2119/// Result of relinquishing a certificate.
2120#[derive(Clone, Debug)]
2121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2122#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2123pub struct RelinquishCertificateResult {
2124    pub relinquished: bool,
2125}
2126
2127// ---------------------------------------------------------------------------
2128// Discovery types
2129// ---------------------------------------------------------------------------
2130
2131/// Information about an entity that issues identity certificates.
2132#[derive(Clone, Debug)]
2133#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2134#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2135pub struct IdentityCertifier {
2136    pub name: String,
2137    pub icon_url: String,
2138    pub description: String,
2139    pub trust: u8,
2140}
2141
2142/// An identity certificate with decoded fields and certifier info.
2143#[derive(Clone, Debug)]
2144#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2145#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2146pub struct IdentityCertificate {
2147    #[cfg_attr(feature = "serde", serde(flatten))]
2148    pub certificate: Certificate,
2149    pub certifier_info: IdentityCertifier,
2150    pub publicly_revealed_keyring: HashMap<String, String>,
2151    pub decrypted_fields: HashMap<String, String>,
2152}
2153
2154/// Arguments for discovering certificates by identity key.
2155#[derive(Clone, Debug)]
2156#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2157#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2158pub struct DiscoverByIdentityKeyArgs {
2159    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2160    pub identity_key: PublicKey,
2161    #[cfg_attr(
2162        feature = "serde",
2163        serde(default, skip_serializing_if = "Option::is_none")
2164    )]
2165    pub limit: Option<u32>,
2166    #[cfg_attr(
2167        feature = "serde",
2168        serde(default, skip_serializing_if = "Option::is_none")
2169    )]
2170    pub offset: Option<u32>,
2171    #[cfg_attr(
2172        feature = "serde",
2173        serde(default, skip_serializing_if = "Option::is_none")
2174    )]
2175    pub seek_permission: Option<bool>,
2176}
2177
2178/// Arguments for discovering certificates by attributes.
2179#[derive(Clone, Debug)]
2180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2181#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2182pub struct DiscoverByAttributesArgs {
2183    pub attributes: HashMap<String, String>,
2184    #[cfg_attr(
2185        feature = "serde",
2186        serde(default, skip_serializing_if = "Option::is_none")
2187    )]
2188    pub limit: Option<u32>,
2189    #[cfg_attr(
2190        feature = "serde",
2191        serde(default, skip_serializing_if = "Option::is_none")
2192    )]
2193    pub offset: Option<u32>,
2194    #[cfg_attr(
2195        feature = "serde",
2196        serde(default, skip_serializing_if = "Option::is_none")
2197    )]
2198    pub seek_permission: Option<bool>,
2199}
2200
2201/// Paginated list of identity certificates found during discovery.
2202#[derive(Clone, Debug)]
2203#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2204#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2205pub struct DiscoverCertificatesResult {
2206    pub total_certificates: u32,
2207    pub certificates: Vec<IdentityCertificate>,
2208}
2209
2210// ---------------------------------------------------------------------------
2211// Key linkage types
2212// ---------------------------------------------------------------------------
2213
2214/// Arguments for revealing key linkage between counterparties.
2215#[derive(Clone, Debug)]
2216#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2217#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2218pub struct RevealCounterpartyKeyLinkageArgs {
2219    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2220    pub counterparty: PublicKey,
2221    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2222    pub verifier: PublicKey,
2223    #[cfg_attr(
2224        feature = "serde",
2225        serde(default, skip_serializing_if = "Option::is_none")
2226    )]
2227    pub privileged: Option<bool>,
2228    #[cfg_attr(
2229        feature = "serde",
2230        serde(default, skip_serializing_if = "Option::is_none")
2231    )]
2232    pub privileged_reason: Option<String>,
2233}
2234
2235/// Result of revealing counterparty key linkage.
2236#[derive(Clone, Debug)]
2237#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2238#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2239pub struct RevealCounterpartyKeyLinkageResult {
2240    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2241    pub prover: PublicKey,
2242    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2243    pub counterparty: PublicKey,
2244    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2245    pub verifier: PublicKey,
2246    pub revelation_time: String,
2247    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
2248    pub encrypted_linkage: Vec<u8>,
2249    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
2250    pub encrypted_linkage_proof: Vec<u8>,
2251}
2252
2253/// Arguments for revealing specific key linkage.
2254#[derive(Clone, Debug)]
2255#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2256#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2257pub struct RevealSpecificKeyLinkageArgs {
2258    pub counterparty: Counterparty,
2259    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2260    pub verifier: PublicKey,
2261    #[cfg_attr(feature = "serde", serde(rename = "protocolID"))]
2262    pub protocol_id: Protocol,
2263    #[cfg_attr(feature = "serde", serde(rename = "keyID"))]
2264    pub key_id: String,
2265    #[cfg_attr(
2266        feature = "serde",
2267        serde(default, skip_serializing_if = "Option::is_none")
2268    )]
2269    pub privileged: Option<bool>,
2270    #[cfg_attr(
2271        feature = "serde",
2272        serde(default, skip_serializing_if = "Option::is_none")
2273    )]
2274    pub privileged_reason: Option<String>,
2275}
2276
2277/// Result of revealing specific key linkage.
2278#[derive(Clone, Debug)]
2279#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2280#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2281pub struct RevealSpecificKeyLinkageResult {
2282    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
2283    pub encrypted_linkage: Vec<u8>,
2284    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_array"))]
2285    pub encrypted_linkage_proof: Vec<u8>,
2286    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2287    pub prover: PublicKey,
2288    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2289    pub verifier: PublicKey,
2290    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::public_key_hex"))]
2291    pub counterparty: PublicKey,
2292    #[cfg_attr(feature = "serde", serde(rename = "protocolID"))]
2293    pub protocol_id: Protocol,
2294    #[cfg_attr(feature = "serde", serde(rename = "keyID"))]
2295    pub key_id: String,
2296    pub proof_type: u8,
2297}
2298
2299// ---------------------------------------------------------------------------
2300// Auth/Info types
2301// ---------------------------------------------------------------------------
2302
2303/// Whether the current session is authenticated.
2304#[derive(Clone, Debug)]
2305#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2306#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2307pub struct AuthenticatedResult {
2308    pub authenticated: bool,
2309}
2310
2311/// Current blockchain height.
2312#[derive(Clone, Debug)]
2313#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2314#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2315pub struct GetHeightResult {
2316    pub height: u32,
2317}
2318
2319/// Arguments for retrieving a blockchain header at a specific height.
2320#[derive(Clone, Debug)]
2321#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2322#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2323pub struct GetHeaderArgs {
2324    pub height: u32,
2325}
2326
2327/// Blockchain header data for the requested height.
2328#[derive(Clone, Debug)]
2329#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2330#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2331pub struct GetHeaderResult {
2332    #[cfg_attr(feature = "serde", serde(with = "serde_helpers::bytes_as_hex"))]
2333    pub header: Vec<u8>,
2334}
2335
2336/// Current blockchain network.
2337#[derive(Clone, Debug)]
2338#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2339#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2340pub struct GetNetworkResult {
2341    pub network: Network,
2342}
2343
2344/// Version information about the wallet implementation.
2345#[derive(Clone, Debug)]
2346#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2347#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2348pub struct GetVersionResult {
2349    pub version: String,
2350}
2351
2352// ---------------------------------------------------------------------------
2353// WalletInterface trait
2354// ---------------------------------------------------------------------------
2355
2356/// The core wallet interface with all 28 async methods.
2357///
2358/// Uses `#[async_trait]` for object safety -- supports both static dispatch
2359/// (`W: WalletInterface`) and dynamic dispatch (`dyn WalletInterface`).
2360///
2361/// Every method takes `originator: Option<&str>` as the last parameter,
2362/// identifying the calling application domain.
2363#[async_trait]
2364pub trait WalletInterface: Send + Sync {
2365    // -- Action methods --
2366
2367    async fn create_action(
2368        &self,
2369        args: CreateActionArgs,
2370        originator: Option<&str>,
2371    ) -> Result<CreateActionResult, WalletError>;
2372
2373    async fn sign_action(
2374        &self,
2375        args: SignActionArgs,
2376        originator: Option<&str>,
2377    ) -> Result<SignActionResult, WalletError>;
2378
2379    async fn abort_action(
2380        &self,
2381        args: AbortActionArgs,
2382        originator: Option<&str>,
2383    ) -> Result<AbortActionResult, WalletError>;
2384
2385    async fn list_actions(
2386        &self,
2387        args: ListActionsArgs,
2388        originator: Option<&str>,
2389    ) -> Result<ListActionsResult, WalletError>;
2390
2391    async fn internalize_action(
2392        &self,
2393        args: InternalizeActionArgs,
2394        originator: Option<&str>,
2395    ) -> Result<InternalizeActionResult, WalletError>;
2396
2397    // -- Output methods --
2398
2399    async fn list_outputs(
2400        &self,
2401        args: ListOutputsArgs,
2402        originator: Option<&str>,
2403    ) -> Result<ListOutputsResult, WalletError>;
2404
2405    async fn relinquish_output(
2406        &self,
2407        args: RelinquishOutputArgs,
2408        originator: Option<&str>,
2409    ) -> Result<RelinquishOutputResult, WalletError>;
2410
2411    // -- Key/Crypto methods --
2412
2413    async fn get_public_key(
2414        &self,
2415        args: GetPublicKeyArgs,
2416        originator: Option<&str>,
2417    ) -> Result<GetPublicKeyResult, WalletError>;
2418
2419    async fn reveal_counterparty_key_linkage(
2420        &self,
2421        args: RevealCounterpartyKeyLinkageArgs,
2422        originator: Option<&str>,
2423    ) -> Result<RevealCounterpartyKeyLinkageResult, WalletError>;
2424
2425    async fn reveal_specific_key_linkage(
2426        &self,
2427        args: RevealSpecificKeyLinkageArgs,
2428        originator: Option<&str>,
2429    ) -> Result<RevealSpecificKeyLinkageResult, WalletError>;
2430
2431    async fn encrypt(
2432        &self,
2433        args: EncryptArgs,
2434        originator: Option<&str>,
2435    ) -> Result<EncryptResult, WalletError>;
2436
2437    async fn decrypt(
2438        &self,
2439        args: DecryptArgs,
2440        originator: Option<&str>,
2441    ) -> Result<DecryptResult, WalletError>;
2442
2443    async fn create_hmac(
2444        &self,
2445        args: CreateHmacArgs,
2446        originator: Option<&str>,
2447    ) -> Result<CreateHmacResult, WalletError>;
2448
2449    async fn verify_hmac(
2450        &self,
2451        args: VerifyHmacArgs,
2452        originator: Option<&str>,
2453    ) -> Result<VerifyHmacResult, WalletError>;
2454
2455    async fn create_signature(
2456        &self,
2457        args: CreateSignatureArgs,
2458        originator: Option<&str>,
2459    ) -> Result<CreateSignatureResult, WalletError>;
2460
2461    async fn verify_signature(
2462        &self,
2463        args: VerifySignatureArgs,
2464        originator: Option<&str>,
2465    ) -> Result<VerifySignatureResult, WalletError>;
2466
2467    // -- Certificate methods --
2468
2469    async fn acquire_certificate(
2470        &self,
2471        args: AcquireCertificateArgs,
2472        originator: Option<&str>,
2473    ) -> Result<Certificate, WalletError>;
2474
2475    async fn list_certificates(
2476        &self,
2477        args: ListCertificatesArgs,
2478        originator: Option<&str>,
2479    ) -> Result<ListCertificatesResult, WalletError>;
2480
2481    async fn prove_certificate(
2482        &self,
2483        args: ProveCertificateArgs,
2484        originator: Option<&str>,
2485    ) -> Result<ProveCertificateResult, WalletError>;
2486
2487    async fn relinquish_certificate(
2488        &self,
2489        args: RelinquishCertificateArgs,
2490        originator: Option<&str>,
2491    ) -> Result<RelinquishCertificateResult, WalletError>;
2492
2493    // -- Discovery methods --
2494
2495    async fn discover_by_identity_key(
2496        &self,
2497        args: DiscoverByIdentityKeyArgs,
2498        originator: Option<&str>,
2499    ) -> Result<DiscoverCertificatesResult, WalletError>;
2500
2501    async fn discover_by_attributes(
2502        &self,
2503        args: DiscoverByAttributesArgs,
2504        originator: Option<&str>,
2505    ) -> Result<DiscoverCertificatesResult, WalletError>;
2506
2507    // -- Auth/Info methods --
2508
2509    async fn is_authenticated(
2510        &self,
2511        originator: Option<&str>,
2512    ) -> Result<AuthenticatedResult, WalletError>;
2513
2514    async fn wait_for_authentication(
2515        &self,
2516        originator: Option<&str>,
2517    ) -> Result<AuthenticatedResult, WalletError>;
2518
2519    async fn get_height(&self, originator: Option<&str>) -> Result<GetHeightResult, WalletError>;
2520
2521    async fn get_header_for_height(
2522        &self,
2523        args: GetHeaderArgs,
2524        originator: Option<&str>,
2525    ) -> Result<GetHeaderResult, WalletError>;
2526
2527    async fn get_network(&self, originator: Option<&str>) -> Result<GetNetworkResult, WalletError>;
2528
2529    async fn get_version(&self, originator: Option<&str>) -> Result<GetVersionResult, WalletError>;
2530}
2531
2532#[cfg(all(test, feature = "serde"))]
2533mod certificate_serde_tests {
2534    //! An UNSIGNED certificate must survive a JSON round-trip.
2535    //!
2536    //! Every optional field is `skip_serializing_if = "Option::is_none"`, so a `None`
2537    //! is OMITTED from the JSON. Without a matching `default`, deserialization then
2538    //! rejects the exact document serialization just produced. That is not a corner
2539    //! case: `signature: None` is the state of every certificate that is being
2540    //! presented to a signer, which is the one time a cert MUST cross a wire.
2541
2542    use super::*;
2543    use crate::primitives::private_key::PrivateKey;
2544
2545    fn unsigned_cert() -> Certificate {
2546        Certificate {
2547            cert_type: CertificateType([0x01; 32]),
2548            serial_number: SerialNumber([0x02; 32]),
2549            subject: PrivateKey::from_random().unwrap().to_public_key(),
2550            certifier: PrivateKey::from_random().unwrap().to_public_key(),
2551            revocation_outpoint: None,
2552            fields: None,
2553            signature: None,
2554        }
2555    }
2556
2557    #[test]
2558    fn unsigned_certificate_round_trips() {
2559        let cert = unsigned_cert();
2560        let json = serde_json::to_string(&cert).expect("serialize");
2561        let back: Certificate = serde_json::from_str(&json).expect(
2562            "an unsigned certificate must deserialize from its own serialization — a signer \
2563             cannot be handed a cert body it is unable to parse",
2564        );
2565        assert_eq!(back.cert_type.0, cert.cert_type.0);
2566        assert_eq!(back.serial_number.0, cert.serial_number.0);
2567        assert_eq!(back.subject, cert.subject);
2568        assert_eq!(back.certifier, cert.certifier);
2569        assert!(back.revocation_outpoint.is_none());
2570        assert!(back.fields.is_none());
2571        assert!(back.signature.is_none());
2572    }
2573
2574    #[test]
2575    fn absent_optional_keys_deserialize_as_none() {
2576        // The omitted keys really are absent — this is what the signer receives.
2577        let json = serde_json::to_string(&unsigned_cert()).unwrap();
2578        assert!(!json.contains("signature"), "None must be omitted: {json}");
2579        assert!(!json.contains("revocationOutpoint"), "None must be omitted");
2580        assert!(!json.contains("fields"), "None must be omitted");
2581
2582        let back: Certificate = serde_json::from_str(&json).unwrap();
2583        assert!(back.signature.is_none());
2584    }
2585
2586    #[test]
2587    fn a_populated_certificate_still_round_trips() {
2588        // The default must not swallow real values.
2589        let mut fields = HashMap::new();
2590        fields.insert("role".to_string(), "admin".to_string());
2591        let cert = Certificate {
2592            revocation_outpoint: Some("aa".repeat(32) + ".1"),
2593            fields: Some(fields),
2594            signature: Some(vec![0xde, 0xad, 0xbe, 0xef]),
2595            ..unsigned_cert()
2596        };
2597        let json = serde_json::to_string(&cert).unwrap();
2598        let back: Certificate = serde_json::from_str(&json).unwrap();
2599        assert_eq!(back.revocation_outpoint, cert.revocation_outpoint);
2600        assert_eq!(back.fields, cert.fields);
2601        assert_eq!(back.signature, Some(vec![0xde, 0xad, 0xbe, 0xef]));
2602    }
2603}
2604
2605#[cfg(test)]
2606mod tests {
2607    use super::*;
2608
2609    #[test]
2610    fn test_serial_number_from_string_hex_valid() {
2611        let hex = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2";
2612        let sn = SerialNumber::from_string(hex).unwrap();
2613        assert_eq!(sn.0[0], 0xa1);
2614        assert_eq!(sn.0[31], 0xb2);
2615    }
2616
2617    #[test]
2618    fn test_serial_number_from_string_base64_valid() {
2619        // 32 bytes of zeros -> base64 is "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
2620        let b64 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
2621        let sn = SerialNumber::from_string(b64).unwrap();
2622        assert_eq!(sn.0, [0u8; 32]);
2623    }
2624
2625    #[test]
2626    fn test_serial_number_from_string_base64_nonzero() {
2627        // All 0xFF bytes: base64 = "//////////////////////////////////////////8="
2628        let b64 = "//////////////////////////////////////////8=";
2629        let sn = SerialNumber::from_string(b64).unwrap();
2630        assert_eq!(sn.0, [0xffu8; 32]);
2631    }
2632
2633    #[test]
2634    fn test_serial_number_from_string_invalid_length() {
2635        assert!(SerialNumber::from_string("abc").is_err());
2636        assert!(SerialNumber::from_string("").is_err());
2637        assert!(SerialNumber::from_string("a1b2c3").is_err());
2638    }
2639
2640    #[test]
2641    fn test_serial_number_from_string_invalid_chars() {
2642        // 64 chars but not valid hex
2643        let bad_hex = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
2644        assert!(SerialNumber::from_string(bad_hex).is_err());
2645    }
2646}
2647
2648#[cfg(test)]
2649mod review_action_result_tests {
2650    use super::*;
2651
2652    #[test]
2653    fn test_review_action_result_status_values() {
2654        assert_eq!(ReviewActionResultStatus::Success.as_str(), "success");
2655        assert_eq!(
2656            ReviewActionResultStatus::DoubleSpend.as_str(),
2657            "doubleSpend"
2658        );
2659        assert_eq!(
2660            ReviewActionResultStatus::ServiceError.as_str(),
2661            "serviceError"
2662        );
2663        assert_eq!(ReviewActionResultStatus::InvalidTx.as_str(), "invalidTx");
2664    }
2665
2666    #[cfg(feature = "serde")]
2667    #[test]
2668    fn test_review_action_result_serde_roundtrip() {
2669        let r = ReviewActionResult {
2670            txid: "aabb".to_string(),
2671            status: ReviewActionResultStatus::DoubleSpend,
2672            competing_txs: Some(vec!["ccdd".to_string()]),
2673            competing_beef: None,
2674        };
2675        let json = serde_json::to_string(&r).unwrap();
2676        assert!(json.contains("\"doubleSpend\""));
2677        assert!(json.contains("\"competingTxs\""));
2678        // competing_beef should be omitted since None
2679        assert!(!json.contains("\"competingBeef\""));
2680        let r2: ReviewActionResult = serde_json::from_str(&json).unwrap();
2681        assert_eq!(r2.status, ReviewActionResultStatus::DoubleSpend);
2682        assert_eq!(r2.competing_txs.unwrap()[0], "ccdd");
2683    }
2684}