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