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                    // Read the variant key as an owned `String`, not a borrowed
197                    // `&str`. Deserializers that don't borrow from a contiguous
198                    // input buffer — e.g. `serde_json::from_value` /
199                    // `from_reader`, and most non-JSON formats — yield owned
200                    // keys that cannot be borrowed as `&str` and would fail here.
201                    let key: alloc::string::String = map
202                        .next_key()?
203                        .ok_or_else(|| de::Error::custom("expected a variant key"))?;
204
205                    let strkey = match key.as_str() {
206                        "public_key_ed25519" => {
207                            let Decoded(inner) = map.next_value()?;
208                            Strkey::PublicKeyEd25519(inner)
209                        }
210                        "pre_auth_tx" => {
211                            let Decoded(inner) = map.next_value()?;
212                            Strkey::PreAuthTx(inner)
213                        }
214                        "hash_x" => {
215                            let Decoded(inner) = map.next_value()?;
216                            Strkey::HashX(inner)
217                        }
218                        "muxed_account_ed25519" => {
219                            let Decoded(inner) = map.next_value()?;
220                            Strkey::MuxedAccountEd25519(inner)
221                        }
222                        "signed_payload_ed25519" => {
223                            let Decoded(inner) = map.next_value()?;
224                            Strkey::SignedPayloadEd25519(inner)
225                        }
226                        "contract" => {
227                            let Decoded(inner) = map.next_value()?;
228                            Strkey::Contract(inner)
229                        }
230                        "liquidity_pool" => {
231                            let Decoded(inner) = map.next_value()?;
232                            Strkey::LiquidityPool(inner)
233                        }
234                        "claimable_balance" => {
235                            let Decoded(inner) = map.next_value()?;
236                            Strkey::ClaimableBalance(inner)
237                        }
238                        _ => {
239                            return Err(de::Error::unknown_variant(
240                                &key,
241                                &[
242                                    "public_key_ed25519",
243                                    "pre_auth_tx",
244                                    "hash_x",
245                                    "muxed_account_ed25519",
246                                    "signed_payload_ed25519",
247                                    "contract",
248                                    "liquidity_pool",
249                                    "claimable_balance",
250                                ],
251                            ))
252                        }
253                    };
254
255                    if map.next_key::<de::IgnoredAny>()?.is_some() {
256                        return Err(de::Error::custom("expected exactly one variant key"));
257                    }
258
259                    Ok(Decoded(strkey))
260                }
261            }
262
263            deserializer.deserialize_map(StrkeyVisitor)
264        }
265    }
266}
267
268/// A pre-authorized transaction signer (`T...`).
269#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
270#[cfg_attr(
271    feature = "serde",
272    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
273)]
274pub struct PreAuthTx(pub [u8; 32]);
275
276impl Debug for PreAuthTx {
277    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
278        write!(f, "PreAuthTx(")?;
279        for b in &self.0 {
280            write!(f, "{b:02x}")?;
281        }
282        write!(f, ")")
283    }
284}
285
286impl PreAuthTx {
287    pub(crate) const PAYLOAD_LEN: usize = 32;
288    pub(crate) const BINARY_LEN: usize = binary_len(Self::PAYLOAD_LEN);
289    pub const ENCODED_LEN: usize = encode_len(Self::BINARY_LEN);
290    const _ASSERTS: () = {
291        assert!(Self::BINARY_LEN == 35);
292        assert!(Self::ENCODED_LEN == 56);
293    };
294
295    pub fn to_string(&self) -> HeaplessString<{ Self::ENCODED_LEN }> {
296        encode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }, { Self::ENCODED_LEN }>(
297            version::PRE_AUTH_TX,
298            &self.0,
299        )
300    }
301
302    fn from_payload(payload: &[u8]) -> Result<Self, DecodeError> {
303        Ok(Self(
304            payload
305                .try_into()
306                .map_err(|_| DecodeError::InvalidPayloadLength)?,
307        ))
308    }
309
310    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
311        Self::from_slice(s.as_bytes())
312    }
313
314    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
315        let (ver, payload) = decode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }>(s)?;
316        match ver {
317            version::PRE_AUTH_TX => Self::from_payload(&payload),
318            _ => Err(DecodeError::UnsupportedVersion),
319        }
320    }
321}
322
323impl Display for PreAuthTx {
324    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
325        write!(f, "{}", self.to_string())
326    }
327}
328
329impl FromStr for PreAuthTx {
330    type Err = DecodeError;
331
332    fn from_str(s: &str) -> Result<Self, Self::Err> {
333        PreAuthTx::from_string(s)
334    }
335}
336
337#[cfg(feature = "serde-decoded")]
338mod pre_auth_tx_decoded_serde_impl {
339    use super::*;
340    use crate::decoded_json_format::Decoded;
341    use serde::{Deserialize, Deserializer, Serialize, Serializer};
342    use serde_with::serde_as;
343
344    #[serde_as]
345    #[derive(Serialize)]
346    #[serde(transparent)]
347    struct DecodedBorrowed<'a>(#[serde_as(as = "serde_with::hex::Hex")] &'a [u8; 32]);
348
349    #[serde_as]
350    #[derive(Deserialize)]
351    #[serde(transparent)]
352    struct DecodedOwned(#[serde_as(as = "serde_with::hex::Hex")] [u8; 32]);
353
354    impl Serialize for Decoded<&PreAuthTx> {
355        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
356            let Self(PreAuthTx(bytes)) = self;
357            DecodedBorrowed(bytes).serialize(serializer)
358        }
359    }
360
361    impl<'de> Deserialize<'de> for Decoded<PreAuthTx> {
362        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
363            let DecodedOwned(bytes) = DecodedOwned::deserialize(deserializer)?;
364            Ok(Decoded(PreAuthTx(bytes)))
365        }
366    }
367}
368
369/// A hash-x signer (`X...`).
370#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
371#[cfg_attr(
372    feature = "serde",
373    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
374)]
375pub struct HashX(pub [u8; 32]);
376
377impl Debug for HashX {
378    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
379        write!(f, "HashX(")?;
380        for b in &self.0 {
381            write!(f, "{b:02x}")?;
382        }
383        write!(f, ")")
384    }
385}
386
387impl HashX {
388    pub(crate) const PAYLOAD_LEN: usize = 32;
389    pub(crate) const BINARY_LEN: usize = binary_len(Self::PAYLOAD_LEN);
390    pub const ENCODED_LEN: usize = encode_len(Self::BINARY_LEN);
391    const _ASSERTS: () = {
392        assert!(Self::BINARY_LEN == 35);
393        assert!(Self::ENCODED_LEN == 56);
394    };
395
396    pub fn to_string(&self) -> HeaplessString<{ Self::ENCODED_LEN }> {
397        encode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }, { Self::ENCODED_LEN }>(
398            version::HASH_X,
399            &self.0,
400        )
401    }
402
403    fn from_payload(payload: &[u8]) -> Result<Self, DecodeError> {
404        Ok(Self(
405            payload
406                .try_into()
407                .map_err(|_| DecodeError::InvalidPayloadLength)?,
408        ))
409    }
410
411    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
412        Self::from_slice(s.as_bytes())
413    }
414
415    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
416        let (ver, payload) = decode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }>(s)?;
417        match ver {
418            version::HASH_X => Self::from_payload(&payload),
419            _ => Err(DecodeError::UnsupportedVersion),
420        }
421    }
422}
423
424impl Display for HashX {
425    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
426        write!(f, "{}", self.to_string())
427    }
428}
429
430impl FromStr for HashX {
431    type Err = DecodeError;
432
433    fn from_str(s: &str) -> Result<Self, Self::Err> {
434        HashX::from_string(s)
435    }
436}
437
438#[cfg(feature = "serde-decoded")]
439mod hash_x_decoded_serde_impl {
440    use super::*;
441    use crate::decoded_json_format::Decoded;
442    use serde::{Deserialize, Deserializer, Serialize, Serializer};
443    use serde_with::serde_as;
444
445    #[serde_as]
446    #[derive(Serialize)]
447    #[serde(transparent)]
448    struct DecodedBorrowed<'a>(#[serde_as(as = "serde_with::hex::Hex")] &'a [u8; 32]);
449
450    #[serde_as]
451    #[derive(Deserialize)]
452    #[serde(transparent)]
453    struct DecodedOwned(#[serde_as(as = "serde_with::hex::Hex")] [u8; 32]);
454
455    impl Serialize for Decoded<&HashX> {
456        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
457            let Self(HashX(bytes)) = self;
458            DecodedBorrowed(bytes).serialize(serializer)
459        }
460    }
461
462    impl<'de> Deserialize<'de> for Decoded<HashX> {
463        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
464            let DecodedOwned(bytes) = DecodedOwned::deserialize(deserializer)?;
465            Ok(Decoded(HashX(bytes)))
466        }
467    }
468}
469
470/// A contract identifier (`C...`).
471#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
472#[cfg_attr(
473    feature = "serde",
474    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
475)]
476pub struct Contract(pub [u8; 32]);
477
478impl Debug for Contract {
479    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
480        write!(f, "Contract(")?;
481        for b in &self.0 {
482            write!(f, "{b:02x}")?;
483        }
484        write!(f, ")")
485    }
486}
487
488impl Contract {
489    pub(crate) const PAYLOAD_LEN: usize = 32;
490    pub(crate) const BINARY_LEN: usize = binary_len(Self::PAYLOAD_LEN);
491    pub const ENCODED_LEN: usize = encode_len(Self::BINARY_LEN);
492    const _ASSERTS: () = {
493        assert!(Self::BINARY_LEN == 35);
494        assert!(Self::ENCODED_LEN == 56);
495    };
496
497    pub fn to_string(&self) -> HeaplessString<{ Self::ENCODED_LEN }> {
498        encode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }, { Self::ENCODED_LEN }>(
499            version::CONTRACT,
500            &self.0,
501        )
502    }
503
504    fn from_payload(payload: &[u8]) -> Result<Self, DecodeError> {
505        Ok(Self(
506            payload
507                .try_into()
508                .map_err(|_| DecodeError::InvalidPayloadLength)?,
509        ))
510    }
511
512    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
513        Self::from_slice(s.as_bytes())
514    }
515
516    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
517        let (ver, payload) = decode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }>(s)?;
518        match ver {
519            version::CONTRACT => Self::from_payload(&payload),
520            _ => Err(DecodeError::UnsupportedVersion),
521        }
522    }
523}
524
525impl Display for Contract {
526    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
527        write!(f, "{}", self.to_string())
528    }
529}
530
531impl FromStr for Contract {
532    type Err = DecodeError;
533
534    fn from_str(s: &str) -> Result<Self, Self::Err> {
535        Contract::from_string(s)
536    }
537}
538
539#[cfg(feature = "serde-decoded")]
540mod contract_decoded_serde_impl {
541    use super::*;
542    use crate::decoded_json_format::Decoded;
543    use serde::{Deserialize, Deserializer, Serialize, Serializer};
544    use serde_with::serde_as;
545
546    #[serde_as]
547    #[derive(Serialize)]
548    #[serde(transparent)]
549    struct DecodedBorrowed<'a>(#[serde_as(as = "serde_with::hex::Hex")] &'a [u8; 32]);
550
551    #[serde_as]
552    #[derive(Deserialize)]
553    #[serde(transparent)]
554    struct DecodedOwned(#[serde_as(as = "serde_with::hex::Hex")] [u8; 32]);
555
556    impl Serialize for Decoded<&Contract> {
557        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
558            let Self(Contract(bytes)) = self;
559            DecodedBorrowed(bytes).serialize(serializer)
560        }
561    }
562
563    impl<'de> Deserialize<'de> for Decoded<Contract> {
564        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
565            let DecodedOwned(bytes) = DecodedOwned::deserialize(deserializer)?;
566            Ok(Decoded(Contract(bytes)))
567        }
568    }
569}
570
571/// A liquidity pool identifier (`L...`).
572#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
573#[cfg_attr(
574    feature = "serde",
575    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
576)]
577pub struct LiquidityPool(pub [u8; 32]);
578
579impl Debug for LiquidityPool {
580    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
581        write!(f, "LiquidityPool(")?;
582        for b in &self.0 {
583            write!(f, "{b:02x}")?;
584        }
585        write!(f, ")")
586    }
587}
588
589impl LiquidityPool {
590    pub(crate) const PAYLOAD_LEN: usize = 32;
591    pub(crate) const BINARY_LEN: usize = binary_len(Self::PAYLOAD_LEN);
592    pub const ENCODED_LEN: usize = encode_len(Self::BINARY_LEN);
593    const _ASSERTS: () = {
594        assert!(Self::BINARY_LEN == 35);
595        assert!(Self::ENCODED_LEN == 56);
596    };
597
598    pub fn to_string(&self) -> HeaplessString<{ Self::ENCODED_LEN }> {
599        encode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }, { Self::ENCODED_LEN }>(
600            version::LIQUIDITY_POOL,
601            &self.0,
602        )
603    }
604
605    fn from_payload(payload: &[u8]) -> Result<Self, DecodeError> {
606        Ok(Self(
607            payload
608                .try_into()
609                .map_err(|_| DecodeError::InvalidPayloadLength)?,
610        ))
611    }
612
613    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
614        Self::from_slice(s.as_bytes())
615    }
616
617    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
618        let (ver, payload) = decode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }>(s)?;
619        match ver {
620            version::LIQUIDITY_POOL => Self::from_payload(&payload),
621            _ => Err(DecodeError::UnsupportedVersion),
622        }
623    }
624}
625
626impl Display for LiquidityPool {
627    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
628        write!(f, "{}", self.to_string())
629    }
630}
631
632impl FromStr for LiquidityPool {
633    type Err = DecodeError;
634
635    fn from_str(s: &str) -> Result<Self, Self::Err> {
636        LiquidityPool::from_string(s)
637    }
638}
639
640#[cfg(feature = "serde-decoded")]
641mod liquidity_pool_decoded_serde_impl {
642    use super::*;
643    use crate::decoded_json_format::Decoded;
644    use serde::{Deserialize, Deserializer, Serialize, Serializer};
645    use serde_with::serde_as;
646
647    #[serde_as]
648    #[derive(Serialize)]
649    #[serde(transparent)]
650    struct DecodedBorrowed<'a>(#[serde_as(as = "serde_with::hex::Hex")] &'a [u8; 32]);
651
652    #[serde_as]
653    #[derive(Deserialize)]
654    #[serde(transparent)]
655    struct DecodedOwned(#[serde_as(as = "serde_with::hex::Hex")] [u8; 32]);
656
657    impl Serialize for Decoded<&LiquidityPool> {
658        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
659            let Self(LiquidityPool(bytes)) = self;
660            DecodedBorrowed(bytes).serialize(serializer)
661        }
662    }
663
664    impl<'de> Deserialize<'de> for Decoded<LiquidityPool> {
665        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
666            let DecodedOwned(bytes) = DecodedOwned::deserialize(deserializer)?;
667            Ok(Decoded(LiquidityPool(bytes)))
668        }
669    }
670}
671
672/// A claimable balance identifier (`B...`).
673#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
674#[cfg_attr(
675    feature = "serde",
676    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
677)]
678pub enum ClaimableBalance {
679    V0([u8; 32]),
680}
681
682impl Debug for ClaimableBalance {
683    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
684        write!(f, "ClaimableBalance(")?;
685        match self {
686            Self::V0(v0) => {
687                write!(f, "V0(")?;
688                for b in v0 {
689                    write!(f, "{b:02x}")?;
690                }
691                write!(f, ")")?;
692            }
693        }
694        write!(f, ")")
695    }
696}
697
698impl ClaimableBalance {
699    // Payload: 1 version byte + 32 hash bytes = 33
700    pub(crate) const PAYLOAD_LEN: usize = 1 + 32;
701    pub(crate) const BINARY_LEN: usize = binary_len(Self::PAYLOAD_LEN);
702    pub const ENCODED_LEN: usize = encode_len(Self::BINARY_LEN);
703    const _ASSERTS: () = {
704        assert!(Self::PAYLOAD_LEN == 33);
705        assert!(Self::BINARY_LEN == 36);
706        assert!(Self::ENCODED_LEN == 58);
707    };
708
709    pub fn to_string(&self) -> HeaplessString<{ Self::ENCODED_LEN }> {
710        match self {
711            Self::V0(v0) => {
712                // First byte is zero for v0
713                let mut payload = [0; Self::PAYLOAD_LEN];
714                payload[1..].copy_from_slice(v0);
715                encode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }, { Self::ENCODED_LEN }>(
716                    version::CLAIMABLE_BALANCE,
717                    &payload,
718                )
719            }
720        }
721    }
722
723    fn from_payload(payload: &[u8]) -> Result<Self, DecodeError> {
724        match payload {
725            // First byte is zero for v0
726            [0, rest @ ..] => Ok(Self::V0(
727                rest.try_into()
728                    .map_err(|_| DecodeError::InvalidPayloadLength)?,
729            )),
730            [_, ..] => Err(DecodeError::UnsupportedClaimableBalanceVersion),
731            [] => Err(DecodeError::InvalidPayloadLength),
732        }
733    }
734
735    pub fn from_string(s: &str) -> Result<Self, DecodeError> {
736        Self::from_slice(s.as_bytes())
737    }
738
739    pub fn from_slice(s: &[u8]) -> Result<Self, DecodeError> {
740        let (ver, payload) = decode::<{ Self::PAYLOAD_LEN }, { Self::BINARY_LEN }>(s)?;
741        match ver {
742            version::CLAIMABLE_BALANCE => Self::from_payload(&payload),
743            _ => Err(DecodeError::UnsupportedVersion),
744        }
745    }
746}
747
748impl Display for ClaimableBalance {
749    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
750        write!(f, "{}", self.to_string())
751    }
752}
753
754impl FromStr for ClaimableBalance {
755    type Err = DecodeError;
756
757    fn from_str(s: &str) -> Result<Self, Self::Err> {
758        ClaimableBalance::from_string(s)
759    }
760}
761
762#[cfg(feature = "serde-decoded")]
763mod claimable_balance_decoded_serde_impl {
764    use super::*;
765    use crate::decoded_json_format::Decoded;
766    use serde::{Deserialize, Deserializer, Serialize, Serializer};
767    use serde_with::serde_as;
768
769    #[serde_as]
770    #[derive(Serialize)]
771    #[serde(rename_all = "snake_case")]
772    enum DecodedBorrowed<'a> {
773        V0(#[serde_as(as = "serde_with::hex::Hex")] &'a [u8; 32]),
774    }
775
776    #[serde_as]
777    #[derive(Deserialize)]
778    #[serde(rename_all = "snake_case")]
779    enum DecodedOwned {
780        V0(#[serde_as(as = "serde_with::hex::Hex")] [u8; 32]),
781    }
782
783    impl Serialize for Decoded<&ClaimableBalance> {
784        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
785            match self.0 {
786                ClaimableBalance::V0(bytes) => DecodedBorrowed::V0(bytes).serialize(serializer),
787            }
788        }
789    }
790
791    impl<'de> Deserialize<'de> for Decoded<ClaimableBalance> {
792        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
793            let decoded = DecodedOwned::deserialize(deserializer)?;
794            Ok(Decoded(match decoded {
795                DecodedOwned::V0(bytes) => ClaimableBalance::V0(bytes),
796            }))
797        }
798    }
799}