Skip to main content

ark_rest/conversions/
stream.rs

1//! Stream event type conversions
2
3use 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        // Parse the hex string to PSBT
107        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        // Parse the hex string to PSBT
179        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        // Parse the tree_nonces JSON string into NoncePks
211        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        // Determine BatchTreeEventType from batch_index (simplified mapping)
228        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, // Default to Vtxo
232        };
233
234        // Parse txid
235        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        // Parse tx (PSBT)
248        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        // Parse children map
263        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        // Determine BatchTreeEventType from batch_index (simplified mapping)
296        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, // Default to Vtxo
300        };
301
302        // Parse txid
303        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        // Parse signature
310        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}