1use alloc::{borrow::Cow, str::FromStr};
2use candid::{
3 CandidType,
4 types::{Serializer, Type, TypeInner},
5};
6use ic_stable_structures::storable::{Bound, Storable};
7
8#[derive(Eq, Ord, PartialOrd, PartialEq, Clone, Copy, Debug)]
10pub struct Txid([u8; 32]);
11
12impl CandidType for Txid {
13 fn _ty() -> Type {
14 TypeInner::Text.into()
15 }
16
17 fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
18 where
19 S: Serializer,
20 {
21 let rev = self.0.iter().rev().copied().collect::<Vec<_>>();
22 serializer.serialize_text(&hex::encode(&rev))
23 }
24}
25
26impl FromStr for Txid {
27 type Err = String;
28
29 fn from_str(s: &str) -> Result<Self, Self::Err> {
30 let bytes = bitcoin::Txid::from_str(s).map_err(|_| "Invalid txid".to_string())?;
31 Ok(Self(*AsRef::<[u8; 32]>::as_ref(&bytes)))
32 }
33}
34
35impl Storable for Txid {
36 fn to_bytes(&self) -> Cow<'_, [u8]> {
37 let bytes = bincode::serialize(self).unwrap();
38 Cow::Owned(bytes)
39 }
40
41 fn into_bytes(self) -> Vec<u8> {
42 bincode::serialize(&self).unwrap()
43 }
44
45 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
46 bincode::deserialize(bytes.as_ref()).unwrap()
47 }
48
49 const BOUND: Bound = Bound::Bounded {
50 max_size: 72,
51 is_fixed_size: true,
52 };
53}
54
55impl Into<bitcoin::Txid> for Txid {
56 fn into(self) -> bitcoin::Txid {
57 use bitcoin::hashes::Hash;
58 bitcoin::Txid::from_byte_array(self.0)
59 }
60}
61
62impl From<bitcoin::Txid> for Txid {
63 fn from(txid: bitcoin::Txid) -> Self {
64 Self(*AsRef::<[u8; 32]>::as_ref(&txid))
65 }
66}
67
68impl AsRef<[u8; 32]> for Txid {
69 fn as_ref(&self) -> &[u8; 32] {
70 &self.0
71 }
72}
73
74impl AsRef<[u8]> for Txid {
75 fn as_ref(&self) -> &[u8] {
76 &self.0
77 }
78}
79
80impl core::fmt::Display for Txid {
81 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
82 let rev = self.0.iter().rev().copied().collect::<Vec<_>>();
83 write!(f, "{}", hex::encode(&rev))
84 }
85}
86
87struct TxidVisitor;
88
89impl<'de> serde::de::Visitor<'de> for TxidVisitor {
90 type Value = Txid;
91
92 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
93 write!(formatter, "a Bitcoin Txid")
94 }
95
96 fn visit_str<E>(self, value: &str) -> Result<Txid, E>
97 where
98 E: serde::de::Error,
99 {
100 Txid::from_str(value)
101 .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self))
102 }
103}
104
105impl<'de> serde::Deserialize<'de> for Txid {
106 fn deserialize<D>(deserializer: D) -> Result<Txid, D::Error>
107 where
108 D: serde::de::Deserializer<'de>,
109 {
110 deserializer.deserialize_str(TxidVisitor)
111 }
112}
113
114impl serde::Serialize for Txid {
115 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
116 where
117 S: serde::ser::Serializer,
118 {
119 serializer.serialize_str(&self.to_string())
120 }
121}
122
123impl Txid {
124 pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
125 Ok(Txid(bytes.try_into().map_err(|e| {
126 format!("Invalid bytes for a Bitcoin Txid: {:?}", e)
127 })?))
128 }
129
130 pub const fn zero() -> Self {
131 Txid([0; 32])
132 }
133}
134
135impl Default for Txid {
136 fn default() -> Self {
137 Txid::zero()
138 }
139}
140
141#[doc(hidden)]
142#[derive(
143 Debug, Clone, Default, CandidType, serde::Serialize, serde::Deserialize, Eq, PartialEq,
144)]
145pub struct TxRecord {
146 pub txid: Txid,
147 pub pools: Vec<String>,
148}
149
150impl Storable for TxRecord {
151 fn to_bytes(&self) -> Cow<'_, [u8]> {
152 let bytes = bincode::serialize(self).unwrap();
153 Cow::Owned(bytes)
154 }
155
156 fn into_bytes(self) -> Vec<u8> {
157 bincode::serialize(&self).unwrap()
158 }
159
160 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
161 bincode::deserialize(bytes.as_ref()).unwrap()
162 }
163
164 const BOUND: Bound = Bound::Unbounded;
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn test_bincode_serde() {
173 let txid_hex = "51230fe70deae44a92f8f44a600585e3e57b8c8720a0b67c4c422f579d9ace2a";
174 let txid_bytes = hex::decode(txid_hex).unwrap();
175 let txid = Txid(txid_bytes.try_into().unwrap());
176 let encoded_hex = hex::encode(bincode::serialize(&txid).unwrap());
177 assert_eq!(
178 txid.0,
179 bincode::deserialize::<Txid>(&hex::decode(encoded_hex).unwrap())
180 .unwrap()
181 .0
182 );
183 }
184
185 #[test]
186 fn test_bytes_serde() {
187 let txid_hex = "51230fe70deae44a92f8f44a600585e3e57b8c8720a0b67c4c422f579d9ace2a";
188 let txid_bytes = hex::decode(txid_hex).unwrap();
189 let txid = Txid::from_bytes(&txid_bytes).unwrap();
190 assert_eq!(txid.0, Txid(txid_bytes.try_into().unwrap()).0);
191 }
192}