Skip to main content

ark_core/
vtxo_list.rs

1use crate::server::VirtualTxOutPoint;
2use crate::ExplorerUtxo;
3use crate::Vtxo;
4use bitcoin::Amount;
5use bitcoin::ScriptBuf;
6use std::collections::HashMap;
7use std::time::Duration;
8
9#[derive(Clone, Debug)]
10pub struct VtxoList {
11    // Unspent
12    pre_confirmed: Vec<VirtualTxOutPoint>,
13    confirmed: Vec<VirtualTxOutPoint>,
14    recoverable: Vec<VirtualTxOutPoint>,
15
16    // Spent
17    spent: Vec<VirtualTxOutPoint>,
18}
19
20impl VtxoList {
21    pub fn new(
22        // The dust amount according to the Arkade server. Dust outputs are considered recoverable.
23        dust: Amount,
24        virtual_tx_outpoints: Vec<VirtualTxOutPoint>,
25    ) -> Self {
26        let mut recoverable = Vec::new();
27        let mut spent = Vec::new();
28        let mut pre_confirmed = Vec::new();
29        let mut confirmed = Vec::new();
30        for virtual_tx_outpoint in virtual_tx_outpoints {
31            if virtual_tx_outpoint.is_recoverable(dust) {
32                recoverable.push(virtual_tx_outpoint);
33            } else if virtual_tx_outpoint.is_unrolled
34                || virtual_tx_outpoint.is_spent
35                || virtual_tx_outpoint.is_swept
36            {
37                spent.push(virtual_tx_outpoint);
38            } else if virtual_tx_outpoint.is_preconfirmed {
39                pre_confirmed.push(virtual_tx_outpoint);
40            } else {
41                confirmed.push(virtual_tx_outpoint);
42            }
43        }
44
45        VtxoList {
46            pre_confirmed,
47            confirmed,
48            recoverable,
49            spent,
50        }
51    }
52
53    pub fn all(&self) -> impl Iterator<Item = &VirtualTxOutPoint> {
54        self.all_unspent().chain(self.spent())
55    }
56
57    pub fn all_unspent(&self) -> impl Iterator<Item = &VirtualTxOutPoint> {
58        self.pre_confirmed
59            .iter()
60            .chain(self.confirmed.iter())
61            .chain(self.recoverable.iter())
62    }
63
64    /// VTXOs that are in a state that allows for unilateral exit.
65    ///
66    /// This does _not_ mean that the VTXOs are readily spendable on-chain, just that their ancestor
67    /// chain can still be published.
68    pub fn could_exit_unilaterally(&self) -> impl Iterator<Item = &VirtualTxOutPoint> {
69        self.pre_confirmed.iter().chain(self.confirmed.iter())
70    }
71
72    /// VTXOs that can be spent in an offchain transaction.
73    pub fn spendable_offchain(&self) -> impl Iterator<Item = &VirtualTxOutPoint> {
74        self.pre_confirmed.iter().chain(self.confirmed.iter())
75    }
76
77    pub fn pre_confirmed(&self) -> impl Iterator<Item = &VirtualTxOutPoint> {
78        self.pre_confirmed.iter()
79    }
80
81    pub fn confirmed(&self) -> impl Iterator<Item = &VirtualTxOutPoint> {
82        self.confirmed.iter()
83    }
84
85    /// Returns the list of recoverable VTXOs
86    ///
87    /// A VTXO is recoverable if it:
88    ///
89    /// - has expired;
90    /// - was swept already; or
91    /// - is sub-dust.
92    pub fn recoverable(&self) -> impl Iterator<Item = &VirtualTxOutPoint> {
93        self.recoverable.iter()
94    }
95
96    /// VTXOs that are already on-chain and can be spent unilaterally (the exit path is active).
97    pub fn exit_ready(
98        &self,
99        now: Duration,
100        // Corresponds to every VTXO in `vtxos` which has been found on the blockchain.
101        explorer_utxos: Vec<ExplorerUtxo>,
102        // TODO: We probably shouldn't involve the opinionated `Vtxo` type here.
103        vtxos: HashMap<ScriptBuf, Vtxo>,
104    ) -> impl Iterator<Item = &VirtualTxOutPoint> {
105        self.all_unspent().filter(move |v| {
106            match explorer_utxos
107                .iter()
108                .find(|explorer_utxo| explorer_utxo.outpoint == v.outpoint)
109            {
110                // VTXOs that have been confirmed on the blockchain.
111                Some(ExplorerUtxo {
112                    confirmation_blocktime: Some(confirmation_blocktime),
113                    confirmations,
114                    ..
115                }) => {
116                    // VTXOs with an _active_ exit path. These should be claimed unilaterally.
117                    if let Some(vtxo) = vtxos.get(&v.script) {
118                        vtxo.can_be_claimed_unilaterally_by_owner(
119                            now,
120                            Duration::from_secs(*confirmation_blocktime),
121                            *confirmations,
122                        )
123                    } else {
124                        false
125                    }
126                }
127                _ => false,
128            }
129        })
130    }
131
132    pub fn spent(&self) -> impl Iterator<Item = &VirtualTxOutPoint> {
133        self.spent.iter()
134    }
135}