ark_rest/conversions/
stream.rs1use crate::conversions::parse_sequence_number;
4use crate::conversions::ConversionError;
5use crate::models;
6use ark_core::server::BatchFailed;
7use ark_core::server::BatchFinalizationEvent;
8use ark_core::server::BatchFinalizedEvent;
9use ark_core::server::BatchStartedEvent;
10use ark_core::server::NoncePks;
11use ark_core::server::StreamEvent;
12use ark_core::server::StreamStartedEvent;
13use ark_core::server::TreeNoncesAggregatedEvent;
14use ark_core::server::TreeSignatureEvent;
15use ark_core::server::TreeSigningStartedEvent;
16use ark_core::server::TreeTxEvent;
17use bitcoin::base64;
18use bitcoin::base64::Engine;
19use bitcoin::hex::FromHex;
20use bitcoin::secp256k1::PublicKey;
21use bitcoin::taproot::Signature;
22use bitcoin::Psbt;
23use bitcoin::Txid;
24use std::str::FromStr;
25
26impl TryFrom<models::GetEventStreamResponse> for StreamEvent {
27 type Error = ConversionError;
28
29 fn try_from(response: models::GetEventStreamResponse) -> Result<Self, Self::Error> {
30 if response.heartbeat.is_some() {
31 return Ok(StreamEvent::Heartbeat);
32 } else if let Some(stream_started) = response.stream_started {
33 return Ok(StreamEvent::StreamStarted(StreamStartedEvent {
34 id: stream_started.id.unwrap_or_default(),
35 }));
36 } else if let Some(batch_started) = response.batch_started {
37 return Ok(StreamEvent::BatchStarted(batch_started.try_into()?));
38 } else if let Some(batch_finalization) = response.batch_finalization {
39 return Ok(StreamEvent::BatchFinalization(
40 batch_finalization.try_into()?,
41 ));
42 } else if let Some(batch_finalized) = response.batch_finalized {
43 return Ok(StreamEvent::BatchFinalized(batch_finalized.try_into()?));
44 } else if let Some(batch_failed) = response.batch_failed {
45 return Ok(StreamEvent::BatchFailed(batch_failed.try_into()?));
46 } else if let Some(tree_signing_started) = response.tree_signing_started {
47 return Ok(StreamEvent::TreeSigningStarted(
48 tree_signing_started.try_into()?,
49 ));
50 } else if let Some(tree_nonces_aggregated) = response.tree_nonces_aggregated {
51 return Ok(StreamEvent::TreeNoncesAggregated(
52 tree_nonces_aggregated.try_into()?,
53 ));
54 } else if let Some(tree_tx) = response.tree_tx {
55 return Ok(StreamEvent::TreeTx(tree_tx.try_into()?));
56 } else if let Some(tree_signature) = response.tree_signature {
57 return Ok(StreamEvent::TreeSignature(tree_signature.try_into()?));
58 }
59
60 Err(ConversionError("No event found in response".to_string()))
61 }
62}
63
64impl TryFrom<models::BatchStartedEvent> for BatchStartedEvent {
65 type Error = ConversionError;
66
67 fn try_from(event: models::BatchStartedEvent) -> Result<Self, Self::Error> {
68 let expiry = event
69 .batch_expiry
70 .ok_or_else(|| ConversionError("Missing batch_expiry".to_string()))?;
71 let expiry = i64::from_str(expiry.as_str())
72 .map_err(|e| ConversionError(format!("Could not parse expiry {e:#}")))?;
73 Ok(BatchStartedEvent {
74 id: event
75 .id
76 .ok_or_else(|| ConversionError("Missing batch id".to_string()))?,
77 intent_id_hashes: event.intent_id_hashes.unwrap_or_default(),
78 batch_expiry: parse_sequence_number(expiry)?,
79 })
80 }
81}
82
83impl TryFrom<models::StreamStartedEvent> for StreamStartedEvent {
84 type Error = ConversionError;
85
86 fn try_from(event: models::StreamStartedEvent) -> Result<Self, Self::Error> {
87 Ok(StreamStartedEvent {
88 id: event
89 .id
90 .ok_or_else(|| ConversionError("Missing batch id".to_string()))?,
91 })
92 }
93}
94
95impl TryFrom<models::BatchFinalizationEvent> for BatchFinalizationEvent {
96 type Error = ConversionError;
97
98 fn try_from(event: models::BatchFinalizationEvent) -> Result<Self, Self::Error> {
99 let id = event
100 .id
101 .ok_or_else(|| ConversionError("Missing batch id".to_string()))?;
102 let commitment_tx_hex = event
103 .commitment_tx
104 .ok_or_else(|| ConversionError("Missing commitment_tx".to_string()))?;
105
106 let base64 = &base64::engine::GeneralPurpose::new(
108 &base64::alphabet::STANDARD,
109 base64::engine::GeneralPurposeConfig::new(),
110 );
111
112 let bytes = base64
113 .decode(&commitment_tx_hex)
114 .map_err(|e| ConversionError(format!("Invalid base64 tx: {e}")))?;
115 let commitment_tx =
116 Psbt::deserialize(&bytes).map_err(|e| ConversionError(format!("Invalid PSBT: {e}")))?;
117
118 Ok(BatchFinalizationEvent { id, commitment_tx })
119 }
120}
121
122impl TryFrom<models::BatchFinalizedEvent> for BatchFinalizedEvent {
123 type Error = ConversionError;
124
125 fn try_from(event: models::BatchFinalizedEvent) -> Result<Self, Self::Error> {
126 let id = event
127 .id
128 .ok_or_else(|| ConversionError("Missing batch id".to_string()))?;
129 let commitment_txid_str = event
130 .commitment_txid
131 .ok_or_else(|| ConversionError("Missing commitment_txid".to_string()))?;
132 let commitment_txid = Txid::from_str(&commitment_txid_str)
133 .map_err(|e| ConversionError(format!("Invalid commitment_txid: {e}")))?;
134
135 Ok(BatchFinalizedEvent {
136 id,
137 commitment_txid,
138 })
139 }
140}
141
142impl TryFrom<models::BatchFailedEvent> for BatchFailed {
143 type Error = ConversionError;
144
145 fn try_from(event: models::BatchFailedEvent) -> Result<Self, Self::Error> {
146 Ok(BatchFailed {
147 id: event
148 .id
149 .ok_or_else(|| ConversionError("Missing batch id".to_string()))?,
150 reason: event
151 .reason
152 .ok_or_else(|| ConversionError("Missing reason".to_string()))?,
153 })
154 }
155}
156
157impl TryFrom<models::TreeSigningStartedEvent> for TreeSigningStartedEvent {
158 type Error = ConversionError;
159
160 fn try_from(event: models::TreeSigningStartedEvent) -> Result<Self, Self::Error> {
161 let id = event
162 .id
163 .ok_or_else(|| ConversionError("Missing batch id".to_string()))?;
164
165 let cosigners_pubkeys_str = event
166 .cosigners_pubkeys
167 .ok_or_else(|| ConversionError("Missing cosigners_pubkeys".to_string()))?;
168 let cosigners_pubkeys = cosigners_pubkeys_str
169 .into_iter()
170 .map(|pk_str| pk_str.parse::<PublicKey>())
171 .collect::<Result<Vec<_>, _>>()
172 .map_err(|e| ConversionError(format!("Invalid cosigner pubkey: {e}")))?;
173
174 let unsigned_commitment_tx_hex = event
175 .unsigned_commitment_tx
176 .ok_or_else(|| ConversionError("Missing unsigned_commitment_tx".to_string()))?;
177
178 let base64 = &base64::engine::GeneralPurpose::new(
180 &base64::alphabet::STANDARD,
181 base64::engine::GeneralPurposeConfig::new(),
182 );
183
184 let bytes = base64
185 .decode(&unsigned_commitment_tx_hex)
186 .map_err(|e| ConversionError(format!("Invalid base64 tx: {e}")))?;
187 let unsigned_commitment_tx =
188 Psbt::deserialize(&bytes).map_err(|e| ConversionError(format!("Invalid PSBT: {e}")))?;
189
190 Ok(TreeSigningStartedEvent {
191 id,
192 cosigners_pubkeys,
193 unsigned_commitment_tx,
194 })
195 }
196}
197
198impl TryFrom<models::TreeNoncesAggregatedEvent> for TreeNoncesAggregatedEvent {
199 type Error = ConversionError;
200
201 fn try_from(event: models::TreeNoncesAggregatedEvent) -> Result<Self, Self::Error> {
202 let id = event
203 .id
204 .ok_or_else(|| ConversionError("Missing batch id".to_string()))?;
205
206 let tree_nonces_str = event
207 .tree_nonces
208 .ok_or_else(|| ConversionError("Missing tree_nonces".to_string()))?;
209
210 let tree_nonces = NoncePks::decode(tree_nonces_str)
212 .map_err(|e| ConversionError(format!("Invalid tree_nonces: {e}")))?;
213
214 Ok(TreeNoncesAggregatedEvent { id, tree_nonces })
215 }
216}
217
218impl TryFrom<models::TreeTxEvent> for TreeTxEvent {
219 type Error = ConversionError;
220
221 fn try_from(event: models::TreeTxEvent) -> Result<Self, Self::Error> {
222 let id = event
223 .id
224 .ok_or_else(|| ConversionError("Missing batch id".to_string()))?;
225 let topic = event.topic.unwrap_or_default();
226
227 let batch_tree_event_type = match event.batch_index {
229 Some(0) => ark_core::server::BatchTreeEventType::Vtxo,
230 Some(1) => ark_core::server::BatchTreeEventType::Connector,
231 _ => ark_core::server::BatchTreeEventType::Vtxo, };
233
234 let txid_str = event
236 .txid
237 .ok_or_else(|| ConversionError("Missing txid".to_string()))?;
238
239 let txid = if txid_str.is_empty() {
240 None
241 } else {
242 let txid = Txid::from_str(&txid_str)
243 .map_err(|e| ConversionError(format!("Invalid txid: {e} but was {txid_str}")))?;
244 Some(txid)
245 };
246
247 let tx_hex = event
249 .tx
250 .ok_or_else(|| ConversionError("Missing tx".to_string()))?;
251 let base64 = &base64::engine::GeneralPurpose::new(
252 &base64::alphabet::STANDARD,
253 base64::engine::GeneralPurposeConfig::new(),
254 );
255
256 let bytes = base64
257 .decode(&tx_hex)
258 .map_err(|e| ConversionError(format!("Invalid base64 tx: {e}")))?;
259 let tx =
260 Psbt::deserialize(&bytes).map_err(|e| ConversionError(format!("Invalid PSBT: {e}")))?;
261
262 let children_str = event.children.unwrap_or_default();
264 let mut children = std::collections::HashMap::new();
265 for (output_idx_str, child_txid_str) in children_str {
266 let output_idx = output_idx_str.parse::<u32>().map_err(|e| {
267 ConversionError(format!("Invalid output index '{output_idx_str}': {e}"))
268 })?;
269 let child_txid = Txid::from_str(&child_txid_str).map_err(|e| {
270 ConversionError(format!("Invalid child txid '{child_txid_str}': {e}"))
271 })?;
272 children.insert(output_idx, child_txid);
273 }
274
275 let tx_graph_chunk = ark_core::TxGraphChunk { txid, tx, children };
276
277 Ok(TreeTxEvent {
278 id,
279 topic,
280 batch_tree_event_type,
281 tx_graph_chunk,
282 })
283 }
284}
285
286impl TryFrom<models::TreeSignatureEvent> for TreeSignatureEvent {
287 type Error = ConversionError;
288
289 fn try_from(event: models::TreeSignatureEvent) -> Result<Self, Self::Error> {
290 let id = event
291 .id
292 .ok_or_else(|| ConversionError("Missing batch id".to_string()))?;
293 let topic = event.topic.unwrap_or_default();
294
295 let batch_tree_event_type = match event.batch_index {
297 Some(0) => ark_core::server::BatchTreeEventType::Vtxo,
298 Some(1) => ark_core::server::BatchTreeEventType::Connector,
299 _ => ark_core::server::BatchTreeEventType::Vtxo, };
301
302 let txid_str = event
304 .txid
305 .ok_or_else(|| ConversionError("Missing txid".to_string()))?;
306 let txid =
307 Txid::from_str(&txid_str).map_err(|e| ConversionError(format!("Invalid txid: {e}")))?;
308
309 let signature_hex = event
311 .signature
312 .ok_or_else(|| ConversionError("Missing signature".to_string()))?;
313 let signature_bytes = Vec::from_hex(&signature_hex)
314 .map_err(|e| ConversionError(format!("Invalid signature hex: {e}")))?;
315 let signature = Signature::from_slice(&signature_bytes)
316 .map_err(|e| ConversionError(format!("Invalid signature: {e}")))?;
317
318 Ok(TreeSignatureEvent {
319 id,
320 topic,
321 batch_tree_event_type,
322 txid,
323 signature,
324 })
325 }
326}