1use crate::tx_graph::TxGraphChunk;
4use crate::ArkAddress;
5use crate::Error;
6use crate::ErrorContext;
7use bitcoin::hex::DisplayHex;
8use bitcoin::secp256k1::PublicKey;
9use bitcoin::taproot::Signature;
10use bitcoin::Amount;
11use bitcoin::OutPoint;
12use bitcoin::Psbt;
13use bitcoin::ScriptBuf;
14use bitcoin::Transaction;
15use bitcoin::Txid;
16use bitcoin::XOnlyPublicKey;
17use musig::musig;
18use std::collections::BTreeMap;
19use std::collections::HashMap;
20use std::str::FromStr;
21
22#[derive(Debug, Clone)]
24pub struct NoncePks(HashMap<Txid, musig::PublicNonce>);
25
26impl NoncePks {
27 pub fn new(nonce_pks: HashMap<Txid, musig::PublicNonce>) -> Self {
28 Self(nonce_pks)
29 }
30
31 pub fn get(&self, txid: &Txid) -> Option<musig::PublicNonce> {
33 self.0.get(txid).copied()
34 }
35
36 pub fn encode(&self) -> HashMap<String, String> {
37 self.0
38 .iter()
39 .map(|(k, v)| (k.to_string(), v.serialize().to_lower_hex_string()))
40 .collect()
41 }
42
43 pub fn decode(map: HashMap<String, String>) -> Result<Self, Error> {
44 let map = map
45 .into_iter()
46 .map(|(k, v)| {
47 let key = k
48 .parse()
49 .map_err(Error::ad_hoc)
50 .context("failed to parse TXID")?;
51
52 let value = {
53 let nonce_bytes = bitcoin::hex::FromHex::from_hex(&v)
54 .map_err(Error::ad_hoc)
55 .context("failed to decode public nonce from hex")?;
56 musig::PublicNonce::from_byte_array(&nonce_bytes)
57 .map_err(Error::ad_hoc)
58 .context("failed to decode public nonce from bytes")?
59 };
60
61 Ok((key, value))
62 })
63 .collect::<Result<HashMap<Txid, musig::PublicNonce>, Error>>()?;
64
65 Ok(Self(map))
66 }
67}
68
69#[derive(Debug, Clone)]
72pub struct TreeTxNoncePks(pub HashMap<XOnlyPublicKey, musig::PublicNonce>);
73
74impl TreeTxNoncePks {
75 pub fn new(tree_nonce_pks: HashMap<XOnlyPublicKey, musig::PublicNonce>) -> Self {
76 Self(tree_nonce_pks)
77 }
78
79 pub fn to_pks(&self) -> Vec<musig::PublicNonce> {
80 self.0.values().copied().collect()
81 }
82
83 pub fn encode(&self) -> HashMap<String, String> {
84 self.0
85 .iter()
86 .map(|(k, v)| (k.to_string(), v.serialize().to_lower_hex_string()))
87 .collect()
88 }
89
90 pub fn decode(map: HashMap<String, String>) -> Result<Self, Error> {
91 let map = map
92 .into_iter()
93 .map(|(k, v)| {
94 let key = k
95 .parse()
96 .map_err(Error::ad_hoc)
97 .context("failed to parse PK")?;
98
99 let value = {
100 let nonce_bytes = bitcoin::hex::FromHex::from_hex(&v)
101 .map_err(Error::ad_hoc)
102 .context("failed to decode public nonce from hex")?;
103 musig::PublicNonce::from_byte_array(&nonce_bytes)
104 .map_err(Error::ad_hoc)
105 .context("failed to decode public nonce from bytes")?
106 };
107
108 Ok((key, value))
109 })
110 .collect::<Result<HashMap<XOnlyPublicKey, musig::PublicNonce>, Error>>()?;
111
112 Ok(Self(map))
113 }
114}
115
116#[derive(Debug, Clone, Default)]
118pub struct PartialSigTree(pub HashMap<Txid, musig::PartialSignature>);
119
120impl PartialSigTree {
121 pub fn encode(&self) -> HashMap<String, String> {
122 self.0
123 .iter()
124 .map(|(k, v)| (k.to_string(), v.serialize().to_lower_hex_string()))
125 .collect()
126 }
127
128 pub fn decode(map: HashMap<String, String>) -> Result<Self, Error> {
129 let map = map
130 .into_iter()
131 .map(|(k, v)| {
132 let key = k
133 .parse()
134 .map_err(Error::ad_hoc)
135 .context("failed to parse TXID")?;
136
137 let value = {
138 let sig_bytes = bitcoin::hex::FromHex::from_hex(&v)
139 .map_err(Error::ad_hoc)
140 .context("failed to decode partial signature from hex")?;
141 musig::PartialSignature::from_byte_array(&sig_bytes)
142 .map_err(Error::ad_hoc)
143 .context("failed to decode partial signature from bytes")?
144 };
145
146 Ok((key, value))
147 })
148 .collect::<Result<HashMap<Txid, musig::PartialSignature>, Error>>()?;
149
150 Ok(Self(map))
151 }
152}
153
154#[derive(Debug, Clone, Default)]
155pub struct TxTree {
156 pub nodes: BTreeMap<(usize, usize), TxTreeNode>,
157}
158
159impl TxTree {
160 pub fn new() -> Self {
161 Self {
162 nodes: BTreeMap::new(),
163 }
164 }
165
166 pub fn get_mut(&mut self, level: usize, index: usize) -> Result<&mut TxTreeNode, Error> {
167 self.nodes
168 .get_mut(&(level, index))
169 .ok_or_else(|| Error::ad_hoc("TxTreeNode not found at ({level}, {index})"))
170 }
171
172 pub fn insert(&mut self, node: TxTreeNode, level: usize, index: usize) {
173 self.nodes.insert((level, index), node);
174 }
175
176 pub fn txs(&self) -> impl Iterator<Item = &Transaction> {
177 self.nodes.values().map(|node| &node.tx.unsigned_tx)
178 }
179
180 pub fn get_level(&self, level: usize) -> Vec<&TxTreeNode> {
182 self.nodes
183 .range((level, 0)..(level + 1, 0))
184 .map(|(_, node)| node)
185 .collect()
186 }
187
188 pub fn iter_levels(&self) -> impl Iterator<Item = (usize, Vec<&TxTreeNode>)> {
190 let max_level = self
191 .nodes
192 .keys()
193 .map(|(level, _)| *level)
194 .max()
195 .unwrap_or(0);
196
197 (0..=max_level).map(move |level| {
198 let nodes = self.get_level(level);
199 (level, nodes)
200 })
201 }
202}
203
204#[derive(Debug, Clone)]
205pub struct TxTreeNode {
206 pub txid: Txid,
207 pub tx: Psbt,
208 pub parent_txid: Txid,
209 pub level: i32,
210 pub level_index: i32,
211 pub leaf: bool,
212}
213
214#[derive(Clone)]
215pub struct GetVtxosRequest {
216 reference: GetVtxosRequestReference,
217 filter: Option<GetVtxosRequestFilter>,
218 page: Option<PageRequest>,
219}
220
221#[derive(Debug, Clone, Copy)]
223pub struct PageRequest {
224 pub size: i32,
226 pub index: i32,
228}
229
230impl GetVtxosRequest {
231 pub fn new_for_addresses(addresses: impl Iterator<Item = ArkAddress>) -> Self {
232 let scripts = addresses
233 .flat_map(|a| [a.to_p2tr_script_pubkey()])
234 .collect();
235
236 Self {
237 reference: GetVtxosRequestReference::Scripts(scripts),
238 filter: None,
239 page: None,
240 }
241 }
242
243 pub fn new_for_outpoints(outpoints: &[OutPoint]) -> Self {
244 Self {
245 reference: GetVtxosRequestReference::OutPoints(outpoints.to_vec()),
246 filter: None,
247 page: None,
248 }
249 }
250
251 pub fn spendable_only(self) -> Result<Self, Error> {
252 if self.filter.is_some() {
253 return Err(Error::ad_hoc("GetVtxosRequest filter already set"));
254 }
255
256 Ok(Self {
257 filter: Some(GetVtxosRequestFilter::Spendable),
258 ..self
259 })
260 }
261
262 pub fn spent_only(self) -> Result<Self, Error> {
263 if self.filter.is_some() {
264 return Err(Error::ad_hoc("GetVtxosRequest filter already set"));
265 }
266
267 Ok(Self {
268 filter: Some(GetVtxosRequestFilter::Spent),
269 ..self
270 })
271 }
272
273 pub fn recoverable_only(self) -> Result<Self, Error> {
274 if self.filter.is_some() {
275 return Err(Error::ad_hoc("GetVtxosRequest filter already set"));
276 }
277
278 Ok(Self {
279 filter: Some(GetVtxosRequestFilter::Recoverable),
280 ..self
281 })
282 }
283
284 pub fn pending_only(self) -> Result<Self, Error> {
285 if self.filter.is_some() {
286 return Err(Error::ad_hoc("GetVtxosRequest filter already set"));
287 }
288
289 Ok(Self {
290 filter: Some(GetVtxosRequestFilter::PendingOnly),
291 ..self
292 })
293 }
294
295 pub fn reference(&self) -> &GetVtxosRequestReference {
296 &self.reference
297 }
298
299 pub fn filter(&self) -> Option<&GetVtxosRequestFilter> {
300 self.filter.as_ref()
301 }
302
303 pub fn with_page(self, size: i32, index: i32) -> Self {
304 Self {
305 page: Some(PageRequest { size, index }),
306 ..self
307 }
308 }
309
310 pub fn page(&self) -> Option<PageRequest> {
311 self.page
312 }
313}
314
315#[derive(Clone)]
316pub enum GetVtxosRequestReference {
317 Scripts(Vec<ScriptBuf>),
318 OutPoints(Vec<OutPoint>),
319}
320
321impl GetVtxosRequestReference {
322 pub fn is_empty(&self) -> bool {
323 match self {
324 GetVtxosRequestReference::Scripts(script_bufs) => script_bufs.is_empty(),
325 GetVtxosRequestReference::OutPoints(outpoints) => outpoints.is_empty(),
326 }
327 }
328}
329
330#[derive(Clone, Copy)]
331pub enum GetVtxosRequestFilter {
332 Spendable,
333 Spent,
334 Recoverable,
335 PendingOnly,
336}
337
338#[derive(Clone, Debug, PartialEq)]
339pub struct VirtualTxOutPoint {
340 pub outpoint: OutPoint,
341 pub created_at: i64,
342 pub expires_at: i64,
343 pub amount: Amount,
344 pub script: ScriptBuf,
345 pub is_preconfirmed: bool,
348 pub is_swept: bool,
349 pub is_unrolled: bool,
350 pub is_spent: bool,
351 pub spent_by: Option<Txid>,
356 pub commitment_txids: Vec<Txid>,
358 pub settled_by: Option<Txid>,
360 pub ark_txid: Option<Txid>,
362}
363
364impl VirtualTxOutPoint {
365 pub fn is_recoverable(&self, dust: Amount) -> bool {
371 if self.is_spent {
372 return false;
373 }
374
375 self.amount < dust || self.is_swept || self.is_expired()
376 }
377
378 pub fn is_expired(&self) -> bool {
386 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
387 let current_timestamp = std::time::SystemTime::now()
388 .duration_since(std::time::UNIX_EPOCH)
389 .expect("valid duration")
390 .as_secs() as i64;
391
392 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
393 let current_timestamp = (js_sys::Date::now() / 1000.0) as i64;
394
395 current_timestamp > self.expires_at && !self.is_swept && !self.is_spent
396 }
397}
398
399#[derive(Clone, Debug)]
400pub struct Info {
401 pub version: String,
402 pub signer_pk: PublicKey,
403 pub forfeit_pk: PublicKey,
404 pub forfeit_address: bitcoin::Address,
405 pub checkpoint_tapscript: ScriptBuf,
406 pub network: bitcoin::Network,
407 pub session_duration: u64,
408 pub unilateral_exit_delay: bitcoin::Sequence,
409 pub boarding_exit_delay: bitcoin::Sequence,
410 pub utxo_min_amount: Option<Amount>,
411 pub utxo_max_amount: Option<Amount>,
412 pub vtxo_min_amount: Option<Amount>,
413 pub vtxo_max_amount: Option<Amount>,
414 pub dust: Amount,
415 pub fees: Option<FeeInfo>,
416 pub scheduled_session: Option<ScheduledSession>,
417 pub deprecated_signers: Vec<DeprecatedSigner>,
418 pub service_status: HashMap<String, String>,
419 pub digest: String,
420}
421
422#[derive(Clone, Debug)]
424pub struct FeeInfo {
425 pub intent_fee: IntentFeeInfo,
426 pub tx_fee_rate: String,
427}
428
429#[derive(Clone, Debug, Default)]
434pub struct IntentFeeInfo {
435 pub offchain_input: Option<String>,
436 pub offchain_output: Option<String>,
437 pub onchain_input: Option<String>,
438 pub onchain_output: Option<String>,
439}
440
441#[derive(Clone, Debug)]
442pub struct ScheduledSession {
443 pub next_start_time: i64,
444 pub next_end_time: i64,
445 pub period: i64,
446 pub duration: i64,
447 pub fees: Option<FeeInfo>,
448}
449
450#[derive(Clone, Debug)]
451pub struct DeprecatedSigner {
452 pub pk: PublicKey,
453 pub cutoff_date: i64,
454}
455
456#[derive(Debug, Clone)]
457pub struct BatchStartedEvent {
458 pub id: String,
459 pub intent_id_hashes: Vec<String>,
460 pub batch_expiry: bitcoin::Sequence,
461}
462
463#[derive(Debug, Clone)]
464pub struct BatchFinalizationEvent {
465 pub id: String,
466 pub commitment_tx: Psbt,
467}
468
469#[derive(Debug, Clone)]
470pub struct BatchFinalizedEvent {
471 pub id: String,
472 pub commitment_txid: Txid,
473}
474
475#[derive(Debug, Clone)]
476pub struct BatchFailed {
477 pub id: String,
478 pub reason: String,
479}
480
481#[derive(Debug, Clone)]
482pub struct TreeSigningStartedEvent {
483 pub id: String,
484 pub cosigners_pubkeys: Vec<PublicKey>,
485 pub unsigned_commitment_tx: Psbt,
486}
487
488#[derive(Debug, Clone)]
489pub struct TreeNoncesAggregatedEvent {
490 pub id: String,
491 pub tree_nonces: NoncePks,
492}
493
494#[derive(Debug, Clone)]
495pub struct TreeTxEvent {
496 pub id: String,
497 pub topic: Vec<String>,
498 pub batch_tree_event_type: BatchTreeEventType,
499 pub tx_graph_chunk: TxGraphChunk,
500}
501
502#[derive(Debug, Clone)]
503pub struct TreeSignatureEvent {
504 pub id: String,
505 pub topic: Vec<String>,
506 pub batch_tree_event_type: BatchTreeEventType,
507 pub txid: Txid,
508 pub signature: Signature,
509}
510
511#[derive(Debug, Clone)]
512pub struct TreeNoncesEvent {
513 pub id: String,
514 pub topic: Vec<String>,
515 pub txid: Txid,
516 pub nonces: TreeTxNoncePks,
517}
518
519#[derive(Debug, Clone)]
520pub enum BatchTreeEventType {
521 Vtxo,
522 Connector,
523}
524
525#[derive(Debug, Clone)]
526pub enum StreamEvent {
527 BatchStarted(BatchStartedEvent),
528 BatchFinalization(BatchFinalizationEvent),
529 BatchFinalized(BatchFinalizedEvent),
530 BatchFailed(BatchFailed),
531 TreeSigningStarted(TreeSigningStartedEvent),
532 TreeNoncesAggregated(TreeNoncesAggregatedEvent),
533 TreeTx(TreeTxEvent),
534 TreeSignature(TreeSignatureEvent),
535 TreeNonces(TreeNoncesEvent),
536 Heartbeat,
537}
538
539impl StreamEvent {
540 pub fn name(&self) -> String {
541 let s = match self {
542 StreamEvent::BatchStarted(_) => "BatchStarted",
543 StreamEvent::BatchFinalization(_) => "BatchFinalization",
544 StreamEvent::BatchFinalized(_) => "BatchFinalized",
545 StreamEvent::BatchFailed(_) => "BatchFailed",
546 StreamEvent::TreeSigningStarted(_) => "TreeSigningStarted",
547 StreamEvent::TreeNoncesAggregated(_) => "TreeNoncesAggregated",
548 StreamEvent::TreeTx(_) => "TreeTx",
549 StreamEvent::TreeSignature(_) => "TreeSignature",
550 StreamEvent::TreeNonces(_) => "TreeNoncesEvent",
551 StreamEvent::Heartbeat => "Heartbeat",
552 };
553
554 s.to_string()
555 }
556}
557
558pub enum StreamTransactionData {
559 Commitment(CommitmentTransaction),
560 Ark(ArkTransaction),
561 Heartbeat,
562}
563
564pub struct ArkTransaction {
565 pub txid: Txid,
566 pub spent_vtxos: Vec<VirtualTxOutPoint>,
567 pub unspent_vtxos: Vec<VirtualTxOutPoint>,
568}
569
570pub struct CommitmentTransaction {
571 pub txid: Txid,
572 pub spent_vtxos: Vec<VirtualTxOutPoint>,
573 pub unspent_vtxos: Vec<VirtualTxOutPoint>,
574}
575
576#[derive(Clone, Debug)]
577pub enum SubscriptionResponse {
578 Event(Box<SubscriptionEvent>),
579 Heartbeat,
580}
581
582#[derive(Clone, Debug)]
583pub struct SubscriptionEvent {
584 pub txid: Txid,
585 pub scripts: Vec<ScriptBuf>,
586 pub new_vtxos: Vec<VirtualTxOutPoint>,
587 pub spent_vtxos: Vec<VirtualTxOutPoint>,
588 pub tx: Option<Psbt>,
589 pub checkpoint_txs: HashMap<OutPoint, Txid>,
590}
591
592pub struct VtxoChains {
593 pub inner: Vec<VtxoChain>,
594}
595
596pub struct VtxoChain {
597 pub txid: Txid,
598 pub tx_type: ChainedTxType,
599 pub spends: Vec<Txid>,
600 pub expires_at: i64,
601}
602
603#[derive(Debug)]
604pub enum ChainedTxType {
605 Commitment,
606 Tree,
607 Checkpoint,
608 Ark,
609 Unspecified,
610}
611
612pub struct SubmitOffchainTxResponse {
613 pub signed_ark_tx: Psbt,
614 pub signed_checkpoint_txs: Vec<Psbt>,
615}
616
617#[derive(Debug, Clone)]
618pub struct PendingTx {
619 pub ark_txid: Txid,
620 pub signed_ark_tx: Psbt,
621 pub signed_checkpoint_txs: Vec<Psbt>,
622}
623
624#[derive(Debug, Clone)]
625pub struct FinalizeOffchainTxResponse {}
626
627#[derive(Debug)]
628pub struct VirtualTxsResponse {
629 pub txs: Vec<Psbt>,
630 pub page: Option<IndexerPage>,
631}
632
633#[derive(Debug)]
634pub struct IndexerPage {
635 pub current: i32,
636 pub next: i32,
637 pub total: i32,
638}
639
640#[derive(Clone, Debug)]
641pub enum Network {
642 Bitcoin,
643 Testnet,
644 Testnet4,
645 Signet,
646 Regtest,
647 Mutinynet,
648}
649
650impl From<Network> for bitcoin::Network {
651 fn from(value: Network) -> Self {
652 match value {
653 Network::Bitcoin => bitcoin::Network::Bitcoin,
654 Network::Testnet => bitcoin::Network::Testnet,
655 Network::Testnet4 => bitcoin::Network::Testnet4,
656 Network::Signet => bitcoin::Network::Signet,
657 Network::Regtest => bitcoin::Network::Regtest,
658 Network::Mutinynet => bitcoin::Network::Signet,
659 }
660 }
661}
662
663impl FromStr for Network {
664 type Err = String;
665
666 #[inline]
667 fn from_str(s: &str) -> Result<Self, Self::Err> {
668 match s {
669 "bitcoin" => Ok(Network::Bitcoin),
670 "testnet" => Ok(Network::Testnet),
671 "testnet4" => Ok(Network::Testnet4),
672 "signet" => Ok(Network::Signet),
673 "regtest" => Ok(Network::Regtest),
674 "mutinynet" => Ok(Network::Mutinynet),
675 _ => Err(format!("Unsupported network {}", s.to_owned())),
676 }
677 }
678}
679
680pub fn parse_sequence_number(value: i64) -> Result<bitcoin::Sequence, Error> {
681 const ARBITRARY_SEQUENCE_THRESHOLD: i64 = 512;
687
688 let sequence = if value.is_negative() {
689 return Err(Error::ad_hoc(format!("invalid sequence number: {value}")));
690 } else if value < ARBITRARY_SEQUENCE_THRESHOLD {
691 bitcoin::Sequence::from_height(value as u16)
692 } else {
693 let secs = u32::try_from(value)
694 .map_err(|_| Error::ad_hoc(format!("sequence seconds overflow: {value}")))?;
695
696 bitcoin::Sequence::from_seconds_ceil(secs).map_err(Error::ad_hoc)?
697 };
698
699 Ok(sequence)
700}
701
702pub fn parse_fee_amount(amount_str: Option<String>) -> Amount {
704 amount_str
705 .and_then(|s| {
706 if s.is_empty() {
707 None
708 } else {
709 s.parse::<u64>().ok()
710 }
711 })
712 .map(Amount::from_sat)
713 .unwrap_or(Amount::ZERO)
714}