Skip to main content

stellar_strkey/
strkey.rs

1use core::{
2    fmt::{Debug, Display},
3    str::FromStr,
4};
5
6use heapless::String as HeaplessString;
7
8use crate::{
9    convert::{binary_len, decode, encode, encode_len},
10    ed25519,
11    error::DecodeError,
12    version,
13};
14
15/// A decoded Stellar strkey of any supported type.
16///
17/// The `PrivateKeyEd25519` (`S…`) variant is intentionally not included;
18/// use [`ed25519::PrivateKey`] directly to encode or decode `S…` strkeys.
19#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
20#[cfg_attr(
21    feature = "serde",
22    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
23)]
24pub enum Strkey {
25    PublicKeyEd25519(ed25519::PublicKey),
26    PreAuthTx(PreAuthTx),
27    HashX(HashX),
28    MuxedAccountEd25519(ed25519::MuxedAccount),
29    SignedPayloadEd25519(ed25519::SignedPayload),
30    Contract(Contract),
31    LiquidityPool(LiquidityPool),
32    ClaimableBalance(ClaimableBalance),
33    MuxedContract(MuxedContract),
34}
35
36impl Strkey {
37    // SignedPayload is the longest strkey type.
38    const MAX_PAYLOAD_LEN: usize = ed25519::SignedPayload::MAX_PAYLOAD_LEN;
39    const MAX_BINARY_LEN: usize = binary_len(Self::MAX_PAYLOAD_LEN);
40    pub const MAX_ENCODED_LEN: usize = encode_len(Self::MAX_BINARY_LEN);
41    const _ASSERTS: () = {
42        assert!(Self::MAX_PAYLOAD_LEN == 100);
43        assert!(Self::MAX_BINARY_LEN == 103);
44        assert!(Self::MAX_ENCODED_LEN == 165);
45        // Verify MAX_PAYLOAD_LEN >= all type payload lengths.
46        assert!(Self::MAX_PAYLOAD_LEN >= ed25519::PrivateKey::PAYLOAD_LEN);
47        assert!(Self::MAX_PAYLOAD_LEN >= ed25519::PublicKey::PAYLOAD_LEN);
48        assert!(Self::MAX_PAYLOAD_LEN >= ed25519::MuxedAccount::PAYLOAD_LEN);
49        assert!(Self::MAX_PAYLOAD_LEN >= ed25519::SignedPayload::MAX_PAYLOAD_LEN);
50        assert!(Self::MAX_PAYLOAD_LEN >= PreAuthTx::PAYLOAD_LEN);
51        assert!(Self::MAX_PAYLOAD_LEN >= HashX::PAYLOAD_LEN);
52        assert!(Self::MAX_PAYLOAD_LEN >= Contract::PAYLOAD_LEN);
53        assert!(Self::MAX_PAYLOAD_LEN >= LiquidityPool::PAYLOAD_LEN);
54        assert!(Self::MAX_PAYLOAD_LEN >= ClaimableBalance::PAYLOAD_LEN);
55        assert!(Self::MAX_PAYLOAD_LEN >= MuxedContract::PAYLOAD_LEN);
56        // Verify MAX_BINARY_LEN >= all type binary lengths.
57        assert!(Self::MAX_BINARY_LEN >= ed25519::PrivateKey::BINARY_LEN);
58        assert!(Self::MAX_BINARY_LEN >= ed25519::PublicKey::BINARY_LEN);
59        assert!(Self::MAX_BINARY_LEN >= ed25519::MuxedAccount::BINARY_LEN);
60        assert!(Self::MAX_BINARY_LEN >= ed25519::SignedPayload::MAX_BINARY_LEN);
61        assert!(Self::MAX_BINARY_LEN >= PreAuthTx::BINARY_LEN);
62        assert!(Self::MAX_BINARY_LEN >= HashX::BINARY_LEN);
63        assert!(Self::MAX_BINARY_LEN >= Contract::BINARY_LEN);
64        assert!(Self::MAX_BINARY_LEN >= LiquidityPool::BINARY_LEN);
65        assert!(Self::MAX_BINARY_LEN >= ClaimableBalance::BINARY_LEN);
66        assert!(Self::MAX_BINARY_LEN >= MuxedContract::BINARY_LEN);
67        // Verify MAX_ENCODED_LEN >= all type encoded lengths.
68        assert!(Self::MAX_ENCODED_LEN >= ed25519::PrivateKey::ENCODED_LEN);
69        assert!(Self::MAX_ENCODED_LEN >= ed25519::PublicKey::ENCODED_LEN);
70        assert!(Self::MAX_ENCODED_LEN >= ed25519::MuxedAccount::ENCODED_LEN);
71        assert!(Self::MAX_ENCODED_LEN >= ed25519::SignedPayload::MAX_ENCODED_LEN);
72        assert!(Self::MAX_ENCODED_LEN >= PreAuthTx::ENCODED_LEN);
73        assert!(Self::MAX_ENCODED_LEN >= HashX::ENCODED_LEN);
74        assert!(Self::MAX_ENCODED_LEN >= Contract::ENCODED_LEN);
75        assert!(Self::MAX_ENCODED_LEN >= LiquidityPool::ENCODED_LEN);
76        assert!(Self::MAX_ENCODED_LEN >= ClaimableBalance::ENCODED_LEN);
77        assert!(Self::MAX_ENCODED_LEN >= MuxedContract::ENCODED_LEN);
78    };
79
80    pub fn to_string(&self) -> HeaplessString<{ Self::MAX_ENCODED_LEN }> {
81        let mut s: HeaplessString<{ Self::MAX_ENCODED_LEN }> = HeaplessString::new();
82        match self {
83            Self::PublicKeyEd25519(x) => s.push_str(x.to_string().as_str()).unwrap(),
84            Self::PreAuthTx(x) => s.push_str(x.to_string().as_str()).unwrap(),
85            Self::HashX(x) => s.push_str(x.to_string().as_str()).unwrap(),
86            Self::MuxedAccountEd25519(x) => s.push_str(x.to_string().as_str()).unwrap(),
87            Self::SignedPayloadEd25519(x) => s.push_str(x.to_string().as_str()).unwrap(),
88            Self::Contract(x) => s.push_str(x.to_string().as_str()).unwrap(),
89            Self::LiquidityPool(x) => s.push_str(x.to_string().as_str()).unwrap(),
90            Self::ClaimableBalance(x) => s.push_str(x.to_string().as_str()).unwrap(),
91            Self::MuxedContract(x) => s.push_str(x.to_string().as_str()).unwrap(),
92        }
93        s
94    }
95
96    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
97        Self::from_slice(s.as_bytes())
98    }
99
100    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
101        // Short-circuit `S…` inputs before running the non-zeroizing
102        // `decode()` so private-key bytes never reach unzeroed scratch.
103        // Callers should use [`ed25519::PrivateKey`] to decode `S…` strkeys.
104        if s.first() == Some(&b'S') {
105            return Err(DecodeError::PrivateKey);
106        }
107        let (ver, payload) = decode::<{ Self::MAX_PAYLOAD_LEN }, { Self::MAX_BINARY_LEN }>(s)?;
108        match ver {
109            version::PUBLIC_KEY_ED25519 => Ok(Self::PublicKeyEd25519(
110                ed25519::PublicKey::from_payload(&payload)?,
111            )),
112            version::PRE_AUTH_TX => Ok(Self::PreAuthTx(PreAuthTx::from_payload(&payload)?)),
113            version::HASH_X => Ok(Self::HashX(HashX::from_payload(&payload)?)),
114            version::MUXED_ACCOUNT_ED25519 => Ok(Self::MuxedAccountEd25519(
115                ed25519::MuxedAccount::from_payload(&payload)?,
116            )),
117            version::SIGNED_PAYLOAD_ED25519 => Ok(Self::SignedPayloadEd25519(
118                ed25519::SignedPayload::from_payload(&payload)?,
119            )),
120            version::CONTRACT => Ok(Self::Contract(Contract::from_payload(&payload)?)),
121            version::LIQUIDITY_POOL => {
122                Ok(Self::LiquidityPool(LiquidityPool::from_payload(&payload)?))
123            }
124            version::CLAIMABLE_BALANCE => Ok(Self::ClaimableBalance(
125                ClaimableBalance::from_payload(&payload)?,
126            )),
127            version::MUXED_CONTRACT => {
128                Ok(Self::MuxedContract(MuxedContract::from_payload(&payload)?))
129            }
130            _ => Err(DecodeError::UnsupportedVersion),
131        }
132    }
133}
134
135impl Display for Strkey {
136    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
137        write!(f, "{}", self.to_string())
138    }
139}
140
141impl FromStr for Strkey {
142    type Err = DecodeError;
143
144    fn from_str(s: &str) -> Result<Self, Self::Err> {
145        Strkey::from_string(s)
146    }
147}
148
149#[cfg(feature = "serde-decoded")]
150mod strkey_decoded_serde_impl {
151    use super::*;
152    use crate::decoded_json_format::Decoded;
153    use serde::{
154        de::{self, MapAccess, Visitor},
155        ser::SerializeMap,
156        Deserialize, Deserializer, Serialize, Serializer,
157    };
158
159    impl Serialize for Decoded<&Strkey> {
160        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
161            let mut map = serializer.serialize_map(Some(1))?;
162            match self.0 {
163                Strkey::PublicKeyEd25519(key) => {
164                    map.serialize_entry("public_key_ed25519", &Decoded(key))?;
165                }
166                Strkey::PreAuthTx(key) => {
167                    map.serialize_entry("pre_auth_tx", &Decoded(key))?;
168                }
169                Strkey::HashX(key) => {
170                    map.serialize_entry("hash_x", &Decoded(key))?;
171                }
172                Strkey::MuxedAccountEd25519(key) => {
173                    map.serialize_entry("muxed_account_ed25519", &Decoded(key))?;
174                }
175                Strkey::SignedPayloadEd25519(key) => {
176                    map.serialize_entry("signed_payload_ed25519", &Decoded(key))?;
177                }
178                Strkey::Contract(key) => {
179                    map.serialize_entry("contract", &Decoded(key))?;
180                }
181                Strkey::LiquidityPool(key) => {
182                    map.serialize_entry("liquidity_pool", &Decoded(key))?;
183                }
184                Strkey::ClaimableBalance(key) => {
185                    map.serialize_entry("claimable_balance", &Decoded(key))?;
186                }
187                Strkey::MuxedContract(key) => {
188                    map.serialize_entry("muxed_contract", &Decoded(key))?;
189                }
190            }
191            map.end()
192        }
193    }
194
195    impl<'de> Deserialize<'de> for Decoded<Strkey> {
196        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
197            struct StrkeyVisitor;
198
199            impl<'de> Visitor<'de> for StrkeyVisitor {
200                type Value = Decoded<Strkey>;
201
202                fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
203                    formatter.write_str("a strkey object")
204                }
205
206                fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<Self::Value, M::Error> {
207                    // Read the variant key as an owned `String`, not a borrowed
208                    // `&str`. Deserializers that don't borrow from a contiguous
209                    // input buffer — e.g. `serde_json::from_value` /
210                    // `from_reader`, and most non-JSON formats — yield owned
211                    // keys that cannot be borrowed as `&str` and would fail here.
212                    let key: alloc::string::String = map
213                        .next_key()?
214                        .ok_or_else(|| de::Error::custom("expected a variant key"))?;
215
216                    let strkey = match key.as_str() {
217                        "public_key_ed25519" => {
218                            let Decoded(inner) = map.next_value()?;
219                            Strkey::PublicKeyEd25519(inner)
220                        }
221                        "pre_auth_tx" => {
222                            let Decoded(inner) = map.next_value()?;
223                            Strkey::PreAuthTx(inner)
224                        }
225                        "hash_x" => {
226                            let Decoded(inner) = map.next_value()?;
227                            Strkey::HashX(inner)
228                        }
229                        "muxed_account_ed25519" => {
230                            let Decoded(inner) = map.next_value()?;
231                            Strkey::MuxedAccountEd25519(inner)
232                        }
233                        "signed_payload_ed25519" => {
234                            let Decoded(inner) = map.next_value()?;
235                            Strkey::SignedPayloadEd25519(inner)
236                        }
237                        "contract" => {
238                            let Decoded(inner) = map.next_value()?;
239                            Strkey::Contract(inner)
240                        }
241                        "liquidity_pool" => {
242                            let Decoded(inner) = map.next_value()?;
243                            Strkey::LiquidityPool(inner)
244                        }
245                        "claimable_balance" => {
246                            let Decoded(inner) = map.next_value()?;
247                            Strkey::ClaimableBalance(inner)
248                        }
249                        "muxed_contract" => {
250                            let Decoded(inner) = map.next_value()?;
251                            Strkey::MuxedContract(inner)
252                        }
253                        _ => {
254                            return Err(de::Error::unknown_variant(
255                                &key,
256                                &[
257                                    "public_key_ed25519",
258                                    "pre_auth_tx",
259                                    "hash_x",
260                                    "muxed_account_ed25519",
261                                    "signed_payload_ed25519",
262                                    "contract",
263                                    "liquidity_pool",
264                                    "claimable_balance",
265                                    "muxed_contract",
266                                ],
267                            ))
268                        }
269                    };
270
271                    if map.next_key::<de::IgnoredAny>()?.is_some() {
272                        return Err(de::Error::custom("expected exactly one variant key"));
273                    }
274
275                    Ok(Decoded(strkey))
276                }
277            }
278
279            deserializer.deserialize_map(StrkeyVisitor)
280        }
281    }
282}
283
284/// A pre-authorized transaction signer (`T...`).
285#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
286#[cfg_attr(
287    feature = "serde",
288    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
289)]
290pub struct PreAuthTx(pub [u8; 32]);
291
292impl Debug for PreAuthTx {
293    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
294        write!(f, "PreAuthTx(")?;
295        for b in &self.0 {
296            write!(f, "{b:02x}")?;
297        }
298        write!(f, ")")
299    }
300}
301
302impl PreAuthTx {
303    pub(crate) const PAYLOAD_LEN: usize = 32;
304    pub(crate) const BINARY_LEN: usize = binary_len(Self::PAYLOAD_LEN);
305    pub const ENCODED_LEN: usize = encode_len(Self::BINARY_LEN);
306    const _ASSERTS: () = {
307        assert!(Self::BINARY_LEN == 35);
308        assert!(Self::ENCODED_LEN == 56);
309    };
310
311    pub fn to_string(&self) -> HeaplessString<{ Self::ENCODED_LEN }> {
312        encode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }, { Self::ENCODED_LEN }>(
313            version::PRE_AUTH_TX,
314            &self.0,
315        )
316    }
317
318    fn from_payload(payload: &[u8]) -> Result<Self, DecodeError> {
319        Ok(Self(
320            payload
321                .try_into()
322                .map_err(|_| DecodeError::InvalidPayloadLength)?,
323        ))
324    }
325
326    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
327        Self::from_slice(s.as_bytes())
328    }
329
330    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
331        let (ver, payload) = decode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }>(s)?;
332        match ver {
333            version::PRE_AUTH_TX => Self::from_payload(&payload),
334            _ => Err(DecodeError::UnsupportedVersion),
335        }
336    }
337}
338
339impl Display for PreAuthTx {
340    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
341        write!(f, "{}", self.to_string())
342    }
343}
344
345impl FromStr for PreAuthTx {
346    type Err = DecodeError;
347
348    fn from_str(s: &str) -> Result<Self, Self::Err> {
349        PreAuthTx::from_string(s)
350    }
351}
352
353#[cfg(feature = "serde-decoded")]
354mod pre_auth_tx_decoded_serde_impl {
355    use super::*;
356    use crate::decoded_json_format::Decoded;
357    use serde::{Deserialize, Deserializer, Serialize, Serializer};
358    use serde_with::serde_as;
359
360    #[serde_as]
361    #[derive(Serialize)]
362    #[serde(transparent)]
363    struct DecodedBorrowed<'a>(#[serde_as(as = "serde_with::hex::Hex")] &'a [u8; 32]);
364
365    #[serde_as]
366    #[derive(Deserialize)]
367    #[serde(transparent)]
368    struct DecodedOwned(#[serde_as(as = "serde_with::hex::Hex")] [u8; 32]);
369
370    impl Serialize for Decoded<&PreAuthTx> {
371        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
372            let Self(PreAuthTx(bytes)) = self;
373            DecodedBorrowed(bytes).serialize(serializer)
374        }
375    }
376
377    impl<'de> Deserialize<'de> for Decoded<PreAuthTx> {
378        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
379            let DecodedOwned(bytes) = DecodedOwned::deserialize(deserializer)?;
380            Ok(Decoded(PreAuthTx(bytes)))
381        }
382    }
383}
384
385/// A hash-x signer (`X...`).
386#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
387#[cfg_attr(
388    feature = "serde",
389    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
390)]
391pub struct HashX(pub [u8; 32]);
392
393impl Debug for HashX {
394    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
395        write!(f, "HashX(")?;
396        for b in &self.0 {
397            write!(f, "{b:02x}")?;
398        }
399        write!(f, ")")
400    }
401}
402
403impl HashX {
404    pub(crate) const PAYLOAD_LEN: usize = 32;
405    pub(crate) const BINARY_LEN: usize = binary_len(Self::PAYLOAD_LEN);
406    pub const ENCODED_LEN: usize = encode_len(Self::BINARY_LEN);
407    const _ASSERTS: () = {
408        assert!(Self::BINARY_LEN == 35);
409        assert!(Self::ENCODED_LEN == 56);
410    };
411
412    pub fn to_string(&self) -> HeaplessString<{ Self::ENCODED_LEN }> {
413        encode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }, { Self::ENCODED_LEN }>(
414            version::HASH_X,
415            &self.0,
416        )
417    }
418
419    fn from_payload(payload: &[u8]) -> Result<Self, DecodeError> {
420        Ok(Self(
421            payload
422                .try_into()
423                .map_err(|_| DecodeError::InvalidPayloadLength)?,
424        ))
425    }
426
427    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
428        Self::from_slice(s.as_bytes())
429    }
430
431    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
432        let (ver, payload) = decode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }>(s)?;
433        match ver {
434            version::HASH_X => Self::from_payload(&payload),
435            _ => Err(DecodeError::UnsupportedVersion),
436        }
437    }
438}
439
440impl Display for HashX {
441    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
442        write!(f, "{}", self.to_string())
443    }
444}
445
446impl FromStr for HashX {
447    type Err = DecodeError;
448
449    fn from_str(s: &str) -> Result<Self, Self::Err> {
450        HashX::from_string(s)
451    }
452}
453
454#[cfg(feature = "serde-decoded")]
455mod hash_x_decoded_serde_impl {
456    use super::*;
457    use crate::decoded_json_format::Decoded;
458    use serde::{Deserialize, Deserializer, Serialize, Serializer};
459    use serde_with::serde_as;
460
461    #[serde_as]
462    #[derive(Serialize)]
463    #[serde(transparent)]
464    struct DecodedBorrowed<'a>(#[serde_as(as = "serde_with::hex::Hex")] &'a [u8; 32]);
465
466    #[serde_as]
467    #[derive(Deserialize)]
468    #[serde(transparent)]
469    struct DecodedOwned(#[serde_as(as = "serde_with::hex::Hex")] [u8; 32]);
470
471    impl Serialize for Decoded<&HashX> {
472        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
473            let Self(HashX(bytes)) = self;
474            DecodedBorrowed(bytes).serialize(serializer)
475        }
476    }
477
478    impl<'de> Deserialize<'de> for Decoded<HashX> {
479        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
480            let DecodedOwned(bytes) = DecodedOwned::deserialize(deserializer)?;
481            Ok(Decoded(HashX(bytes)))
482        }
483    }
484}
485
486/// A contract identifier (`C...`).
487#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
488#[cfg_attr(
489    feature = "serde",
490    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
491)]
492pub struct Contract(pub [u8; 32]);
493
494impl Debug for Contract {
495    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
496        write!(f, "Contract(")?;
497        for b in &self.0 {
498            write!(f, "{b:02x}")?;
499        }
500        write!(f, ")")
501    }
502}
503
504impl Contract {
505    pub(crate) const PAYLOAD_LEN: usize = 32;
506    pub(crate) const BINARY_LEN: usize = binary_len(Self::PAYLOAD_LEN);
507    pub const ENCODED_LEN: usize = encode_len(Self::BINARY_LEN);
508    const _ASSERTS: () = {
509        assert!(Self::BINARY_LEN == 35);
510        assert!(Self::ENCODED_LEN == 56);
511    };
512
513    pub fn to_string(&self) -> HeaplessString<{ Self::ENCODED_LEN }> {
514        encode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }, { Self::ENCODED_LEN }>(
515            version::CONTRACT,
516            &self.0,
517        )
518    }
519
520    fn from_payload(payload: &[u8]) -> Result<Self, DecodeError> {
521        Ok(Self(
522            payload
523                .try_into()
524                .map_err(|_| DecodeError::InvalidPayloadLength)?,
525        ))
526    }
527
528    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
529        Self::from_slice(s.as_bytes())
530    }
531
532    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
533        let (ver, payload) = decode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }>(s)?;
534        match ver {
535            version::CONTRACT => Self::from_payload(&payload),
536            _ => Err(DecodeError::UnsupportedVersion),
537        }
538    }
539}
540
541impl Display for Contract {
542    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
543        write!(f, "{}", self.to_string())
544    }
545}
546
547impl FromStr for Contract {
548    type Err = DecodeError;
549
550    fn from_str(s: &str) -> Result<Self, Self::Err> {
551        Contract::from_string(s)
552    }
553}
554
555#[cfg(feature = "serde-decoded")]
556mod contract_decoded_serde_impl {
557    use super::*;
558    use crate::decoded_json_format::Decoded;
559    use serde::{Deserialize, Deserializer, Serialize, Serializer};
560    use serde_with::serde_as;
561
562    #[serde_as]
563    #[derive(Serialize)]
564    #[serde(transparent)]
565    struct DecodedBorrowed<'a>(#[serde_as(as = "serde_with::hex::Hex")] &'a [u8; 32]);
566
567    #[serde_as]
568    #[derive(Deserialize)]
569    #[serde(transparent)]
570    struct DecodedOwned(#[serde_as(as = "serde_with::hex::Hex")] [u8; 32]);
571
572    impl Serialize for Decoded<&Contract> {
573        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
574            let Self(Contract(bytes)) = self;
575            DecodedBorrowed(bytes).serialize(serializer)
576        }
577    }
578
579    impl<'de> Deserialize<'de> for Decoded<Contract> {
580        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
581            let DecodedOwned(bytes) = DecodedOwned::deserialize(deserializer)?;
582            Ok(Decoded(Contract(bytes)))
583        }
584    }
585}
586
587/// A liquidity pool identifier (`L...`).
588#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
589#[cfg_attr(
590    feature = "serde",
591    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
592)]
593pub struct LiquidityPool(pub [u8; 32]);
594
595impl Debug for LiquidityPool {
596    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
597        write!(f, "LiquidityPool(")?;
598        for b in &self.0 {
599            write!(f, "{b:02x}")?;
600        }
601        write!(f, ")")
602    }
603}
604
605impl LiquidityPool {
606    pub(crate) const PAYLOAD_LEN: usize = 32;
607    pub(crate) const BINARY_LEN: usize = binary_len(Self::PAYLOAD_LEN);
608    pub const ENCODED_LEN: usize = encode_len(Self::BINARY_LEN);
609    const _ASSERTS: () = {
610        assert!(Self::BINARY_LEN == 35);
611        assert!(Self::ENCODED_LEN == 56);
612    };
613
614    pub fn to_string(&self) -> HeaplessString<{ Self::ENCODED_LEN }> {
615        encode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }, { Self::ENCODED_LEN }>(
616            version::LIQUIDITY_POOL,
617            &self.0,
618        )
619    }
620
621    fn from_payload(payload: &[u8]) -> Result<Self, DecodeError> {
622        Ok(Self(
623            payload
624                .try_into()
625                .map_err(|_| DecodeError::InvalidPayloadLength)?,
626        ))
627    }
628
629    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
630        Self::from_slice(s.as_bytes())
631    }
632
633    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
634        let (ver, payload) = decode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }>(s)?;
635        match ver {
636            version::LIQUIDITY_POOL => Self::from_payload(&payload),
637            _ => Err(DecodeError::UnsupportedVersion),
638        }
639    }
640}
641
642impl Display for LiquidityPool {
643    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
644        write!(f, "{}", self.to_string())
645    }
646}
647
648impl FromStr for LiquidityPool {
649    type Err = DecodeError;
650
651    fn from_str(s: &str) -> Result<Self, Self::Err> {
652        LiquidityPool::from_string(s)
653    }
654}
655
656#[cfg(feature = "serde-decoded")]
657mod liquidity_pool_decoded_serde_impl {
658    use super::*;
659    use crate::decoded_json_format::Decoded;
660    use serde::{Deserialize, Deserializer, Serialize, Serializer};
661    use serde_with::serde_as;
662
663    #[serde_as]
664    #[derive(Serialize)]
665    #[serde(transparent)]
666    struct DecodedBorrowed<'a>(#[serde_as(as = "serde_with::hex::Hex")] &'a [u8; 32]);
667
668    #[serde_as]
669    #[derive(Deserialize)]
670    #[serde(transparent)]
671    struct DecodedOwned(#[serde_as(as = "serde_with::hex::Hex")] [u8; 32]);
672
673    impl Serialize for Decoded<&LiquidityPool> {
674        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
675            let Self(LiquidityPool(bytes)) = self;
676            DecodedBorrowed(bytes).serialize(serializer)
677        }
678    }
679
680    impl<'de> Deserialize<'de> for Decoded<LiquidityPool> {
681        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
682            let DecodedOwned(bytes) = DecodedOwned::deserialize(deserializer)?;
683            Ok(Decoded(LiquidityPool(bytes)))
684        }
685    }
686}
687
688/// A claimable balance identifier (`B...`).
689#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
690#[cfg_attr(
691    feature = "serde",
692    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
693)]
694pub enum ClaimableBalance {
695    V0([u8; 32]),
696}
697
698impl Debug for ClaimableBalance {
699    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
700        write!(f, "ClaimableBalance(")?;
701        match self {
702            Self::V0(v0) => {
703                write!(f, "V0(")?;
704                for b in v0 {
705                    write!(f, "{b:02x}")?;
706                }
707                write!(f, ")")?;
708            }
709        }
710        write!(f, ")")
711    }
712}
713
714impl ClaimableBalance {
715    // Payload: 1 version byte + 32 hash bytes = 33
716    pub(crate) const PAYLOAD_LEN: usize = 1 + 32;
717    pub(crate) const BINARY_LEN: usize = binary_len(Self::PAYLOAD_LEN);
718    pub const ENCODED_LEN: usize = encode_len(Self::BINARY_LEN);
719    const _ASSERTS: () = {
720        assert!(Self::PAYLOAD_LEN == 33);
721        assert!(Self::BINARY_LEN == 36);
722        assert!(Self::ENCODED_LEN == 58);
723    };
724
725    pub fn to_string(&self) -> HeaplessString<{ Self::ENCODED_LEN }> {
726        match self {
727            Self::V0(v0) => {
728                // First byte is zero for v0
729                let mut payload = [0; Self::PAYLOAD_LEN];
730                payload[1..].copy_from_slice(v0);
731                encode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }, { Self::ENCODED_LEN }>(
732                    version::CLAIMABLE_BALANCE,
733                    &payload,
734                )
735            }
736        }
737    }
738
739    fn from_payload(payload: &[u8]) -> Result<Self, DecodeError> {
740        match payload {
741            // First byte is zero for v0
742            [0, rest @ ..] => Ok(Self::V0(
743                rest.try_into()
744                    .map_err(|_| DecodeError::InvalidPayloadLength)?,
745            )),
746            [_, ..] => Err(DecodeError::UnsupportedClaimableBalanceVersion),
747            [] => Err(DecodeError::InvalidPayloadLength),
748        }
749    }
750
751    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
752        Self::from_slice(s.as_bytes())
753    }
754
755    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
756        let (ver, payload) = decode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }>(s)?;
757        match ver {
758            version::CLAIMABLE_BALANCE => Self::from_payload(&payload),
759            _ => Err(DecodeError::UnsupportedVersion),
760        }
761    }
762}
763
764impl Display for ClaimableBalance {
765    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
766        write!(f, "{}", self.to_string())
767    }
768}
769
770impl FromStr for ClaimableBalance {
771    type Err = DecodeError;
772
773    fn from_str(s: &str) -> Result<Self, Self::Err> {
774        ClaimableBalance::from_string(s)
775    }
776}
777
778#[cfg(feature = "serde-decoded")]
779mod claimable_balance_decoded_serde_impl {
780    use super::*;
781    use crate::decoded_json_format::Decoded;
782    use serde::{Deserialize, Deserializer, Serialize, Serializer};
783    use serde_with::serde_as;
784
785    #[serde_as]
786    #[derive(Serialize)]
787    #[serde(rename_all = "snake_case")]
788    enum DecodedBorrowed<'a> {
789        V0(#[serde_as(as = "serde_with::hex::Hex")] &'a [u8; 32]),
790    }
791
792    #[serde_as]
793    #[derive(Deserialize)]
794    #[serde(rename_all = "snake_case")]
795    enum DecodedOwned {
796        V0(#[serde_as(as = "serde_with::hex::Hex")] [u8; 32]),
797    }
798
799    impl Serialize for Decoded<&ClaimableBalance> {
800        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
801            match self.0 {
802                ClaimableBalance::V0(bytes) => DecodedBorrowed::V0(bytes).serialize(serializer),
803            }
804        }
805    }
806
807    impl<'de> Deserialize<'de> for Decoded<ClaimableBalance> {
808        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
809            let decoded = DecodedOwned::deserialize(deserializer)?;
810            Ok(Decoded(match decoded {
811                DecodedOwned::V0(bytes) => ClaimableBalance::V0(bytes),
812            }))
813        }
814    }
815}
816
817/// A muxed contract address (`W...`): a contract identifier paired with a
818/// 64-bit multiplexing id.
819#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
820#[cfg_attr(
821    feature = "serde",
822    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
823)]
824pub struct MuxedContract {
825    pub contract_id: [u8; 32],
826    pub id: u64,
827}
828
829impl Debug for MuxedContract {
830    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
831        write!(f, "MuxedContract(")?;
832        for b in &self.contract_id {
833            write!(f, "{b:02x}")?;
834        }
835        write!(f, ", {}", self.id)?;
836        write!(f, ")")
837    }
838}
839
840impl MuxedContract {
841    pub(crate) const PAYLOAD_LEN: usize = 32 + 8; // contract id + id
842    pub(crate) const BINARY_LEN: usize = binary_len(Self::PAYLOAD_LEN);
843    pub const ENCODED_LEN: usize = encode_len(Self::BINARY_LEN);
844    const _ASSERTS: () = {
845        assert!(Self::BINARY_LEN == 43);
846        assert!(Self::ENCODED_LEN == 69);
847    };
848
849    pub fn to_string(&self) -> HeaplessString<{ Self::ENCODED_LEN }> {
850        let mut payload: [u8; Self::PAYLOAD_LEN] = [0; Self::PAYLOAD_LEN];
851        let (contract_id, id) = payload.split_at_mut(32);
852        contract_id.copy_from_slice(&self.contract_id);
853        id.copy_from_slice(&self.id.to_be_bytes());
854        encode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }, { Self::ENCODED_LEN }>(
855            version::MUXED_CONTRACT,
856            &payload,
857        )
858    }
859
860    fn from_payload(payload: &[u8]) -> Result<Self, DecodeError> {
861        if payload.len() != Self::PAYLOAD_LEN {
862            return Err(DecodeError::InvalidPayloadLength);
863        }
864        let (contract_id, id) = payload.split_at(32);
865        Ok(Self {
866            contract_id: contract_id
867                .try_into()
868                .map_err(|_| DecodeError::InvalidPayloadLength)?,
869            id: u64::from_be_bytes(
870                id.try_into()
871                    .map_err(|_| DecodeError::InvalidPayloadLength)?,
872            ),
873        })
874    }
875
876    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
877        Self::from_slice(s.as_bytes())
878    }
879
880    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
881        let (ver, payload) = decode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }>(s)?;
882        match ver {
883            version::MUXED_CONTRACT => Self::from_payload(&payload),
884            _ => Err(DecodeError::UnsupportedVersion),
885        }
886    }
887}
888
889impl Display for MuxedContract {
890    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
891        write!(f, "{}", self.to_string())
892    }
893}
894
895impl FromStr for MuxedContract {
896    type Err = DecodeError;
897
898    fn from_str(s: &str) -> Result<Self, Self::Err> {
899        MuxedContract::from_string(s)
900    }
901}
902
903#[cfg(feature = "serde-decoded")]
904mod muxed_contract_decoded_serde_impl {
905    use super::*;
906    use crate::decoded_json_format::Decoded;
907    use serde::{Deserialize, Deserializer, Serialize, Serializer};
908    use serde_with::serde_as;
909
910    #[serde_as]
911    #[derive(Serialize)]
912    struct DecodedBorrowed<'a> {
913        #[serde_as(as = "serde_with::hex::Hex")]
914        contract_id: &'a [u8; 32],
915        id: u64,
916    }
917
918    #[serde_as]
919    #[derive(Deserialize)]
920    struct DecodedOwned {
921        #[serde_as(as = "serde_with::hex::Hex")]
922        contract_id: [u8; 32],
923        id: u64,
924    }
925
926    impl Serialize for Decoded<&MuxedContract> {
927        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
928            let Self(MuxedContract { contract_id, id }) = self;
929            DecodedBorrowed {
930                contract_id,
931                id: *id,
932            }
933            .serialize(serializer)
934        }
935    }
936
937    impl<'de> Deserialize<'de> for Decoded<MuxedContract> {
938        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
939            let DecodedOwned { contract_id, id } = DecodedOwned::deserialize(deserializer)?;
940            Ok(Decoded(MuxedContract { contract_id, id }))
941        }
942    }
943}