titan_types/
inscription_id.rs1use {
2 bitcoin::{hashes::Hash, Txid},
3 borsh::{BorshDeserialize, BorshSerialize},
4 core::str,
5 serde::{Deserialize, Serialize},
6 std::{
7 fmt::{self, Display, Formatter},
8 io::{self, Read, Write},
9 str::FromStr,
10 },
11};
12
13#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
14pub struct InscriptionId {
15 pub txid: Txid,
16 pub index: u32,
17}
18
19impl BorshSerialize for InscriptionId {
20 fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
21 BorshSerialize::serialize(&self.txid.as_raw_hash().to_byte_array(), writer)?;
23
24 BorshSerialize::serialize(&self.index, writer)?;
26
27 Ok(())
28 }
29}
30
31impl BorshDeserialize for InscriptionId {
32 fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
33 let txid_bytes = <[u8; 32]>::deserialize_reader(reader)?;
35
36 let index = u32::deserialize_reader(reader)?;
38
39 Ok(Self {
40 txid: Txid::from_byte_array(txid_bytes),
41 index,
42 })
43 }
44}
45
46impl Display for InscriptionId {
47 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
48 write!(f, "{}i{}", self.txid, self.index)
49 }
50}
51
52#[derive(Debug)]
53pub enum ParseError {
54 Character(char),
55 Length(usize),
56 Separator(char),
57 Txid(bitcoin::hex::HexToArrayError),
58 Index(std::num::ParseIntError),
59}
60
61impl Display for ParseError {
62 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
63 match self {
64 Self::Character(c) => write!(f, "invalid character: '{c}'"),
65 Self::Length(len) => write!(f, "invalid length: {len}"),
66 Self::Separator(c) => write!(f, "invalid separator: `{c}`"),
67 Self::Txid(err) => write!(f, "invalid txid: {err}"),
68 Self::Index(err) => write!(f, "invalid index: {err}"),
69 }
70 }
71}
72
73impl std::error::Error for ParseError {}
74
75impl FromStr for InscriptionId {
76 type Err = ParseError;
77
78 fn from_str(s: &str) -> Result<Self, Self::Err> {
79 if let Some(char) = s.chars().find(|char| !char.is_ascii()) {
80 return Err(ParseError::Character(char));
81 }
82
83 const TXID_LEN: usize = 64;
84 const MIN_LEN: usize = TXID_LEN + 2;
85
86 if s.len() < MIN_LEN {
87 return Err(ParseError::Length(s.len()));
88 }
89
90 let txid = &s[..TXID_LEN];
91
92 let separator = s.chars().nth(TXID_LEN).unwrap();
93
94 if separator != 'i' {
95 return Err(ParseError::Separator(separator));
96 }
97
98 let vout = &s[TXID_LEN + 1..];
99
100 Ok(Self {
101 txid: txid.parse().map_err(ParseError::Txid)?,
102 index: vout.parse().map_err(ParseError::Index)?,
103 })
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use bitcoin::Txid;
110
111 use super::*;
112
113 fn inscription_id(n: u32) -> InscriptionId {
114 let hex = format!("{n:x}");
115
116 if hex.is_empty() || hex.len() > 1 {
117 panic!();
118 }
119
120 format!("{}i{n}", hex.repeat(64)).parse().unwrap()
121 }
122
123 fn txid(n: u32) -> Txid {
124 let hex = format!("{n:x}");
125
126 if hex.is_empty() || hex.len() > 1 {
127 panic!();
128 }
129
130 hex.repeat(64).parse().unwrap()
131 }
132
133 #[test]
134 fn display() {
135 assert_eq!(
136 inscription_id(1).to_string(),
137 "1111111111111111111111111111111111111111111111111111111111111111i1",
138 );
139 assert_eq!(
140 InscriptionId {
141 txid: txid(1),
142 index: 0,
143 }
144 .to_string(),
145 "1111111111111111111111111111111111111111111111111111111111111111i0",
146 );
147 assert_eq!(
148 InscriptionId {
149 txid: txid(1),
150 index: 0xFFFFFFFF,
151 }
152 .to_string(),
153 "1111111111111111111111111111111111111111111111111111111111111111i4294967295",
154 );
155 }
156
157 #[test]
158 fn from_str() {
159 assert_eq!(
160 "1111111111111111111111111111111111111111111111111111111111111111i1"
161 .parse::<InscriptionId>()
162 .unwrap(),
163 inscription_id(1),
164 );
165 assert_eq!(
166 "1111111111111111111111111111111111111111111111111111111111111111i4294967295"
167 .parse::<InscriptionId>()
168 .unwrap(),
169 InscriptionId {
170 txid: txid(1),
171 index: 0xFFFFFFFF,
172 },
173 );
174 assert_eq!(
175 "1111111111111111111111111111111111111111111111111111111111111111i4294967295"
176 .parse::<InscriptionId>()
177 .unwrap(),
178 InscriptionId {
179 txid: txid(1),
180 index: 0xFFFFFFFF,
181 },
182 );
183 }
184}