kaccy_bitcoin/
confirmation.rs

1//! Confirmation tracking for Bitcoin transactions
2
3use bitcoin::Amount;
4use serde::Serialize;
5use std::collections::HashMap;
6use uuid::Uuid;
7
8/// Confirmation levels for payment processing
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
10pub enum ConfirmationLevel {
11    /// Unconfirmed (in mempool)
12    Unconfirmed,
13    /// 1 confirmation - first block
14    OneConfirmation,
15    /// 3 confirmations - reasonably secure
16    ThreeConfirmations,
17    /// 6 confirmations - highly secure
18    SixConfirmations,
19}
20
21impl ConfirmationLevel {
22    /// Create from confirmation count
23    pub fn from_count(confirmations: u32) -> Self {
24        match confirmations {
25            0 => Self::Unconfirmed,
26            1..=2 => Self::OneConfirmation,
27            3..=5 => Self::ThreeConfirmations,
28            _ => Self::SixConfirmations,
29        }
30    }
31
32    /// Get the minimum confirmations for this level
33    pub fn min_confirmations(&self) -> u32 {
34        match self {
35            Self::Unconfirmed => 0,
36            Self::OneConfirmation => 1,
37            Self::ThreeConfirmations => 3,
38            Self::SixConfirmations => 6,
39        }
40    }
41
42    /// Check if this level meets a required threshold
43    pub fn meets_threshold(&self, required: ConfirmationLevel) -> bool {
44        *self >= required
45    }
46
47    /// Get human-readable description
48    pub fn description(&self) -> &'static str {
49        match self {
50            Self::Unconfirmed => "Unconfirmed (waiting for first block)",
51            Self::OneConfirmation => "1 confirmation (initial confirmation)",
52            Self::ThreeConfirmations => "3+ confirmations (reasonably secure)",
53            Self::SixConfirmations => "6+ confirmations (highly secure)",
54        }
55    }
56}
57
58/// Tracked order with confirmation status
59#[derive(Debug, Clone)]
60pub struct TrackedOrder {
61    /// Order ID
62    pub order_id: Uuid,
63    /// Payment address
64    pub address: String,
65    /// Expected amount
66    pub expected_amount: Amount,
67    /// Transaction ID (if known)
68    pub txid: Option<String>,
69    /// Current confirmation count
70    pub confirmations: u32,
71    /// Current confirmation level
72    pub level: ConfirmationLevel,
73    /// Previous level (for change detection)
74    pub previous_level: ConfirmationLevel,
75    /// Received amount
76    pub received_amount: Option<Amount>,
77}
78
79impl TrackedOrder {
80    /// Create a new tracked order
81    pub fn new(order_id: Uuid, address: String, expected_amount: Amount) -> Self {
82        Self {
83            order_id,
84            address,
85            expected_amount,
86            txid: None,
87            confirmations: 0,
88            level: ConfirmationLevel::Unconfirmed,
89            previous_level: ConfirmationLevel::Unconfirmed,
90            received_amount: None,
91        }
92    }
93
94    /// Update confirmation count and detect level changes
95    pub fn update_confirmations(&mut self, confirmations: u32) -> Option<ConfirmationEvent> {
96        self.confirmations = confirmations;
97        let new_level = ConfirmationLevel::from_count(confirmations);
98
99        if new_level != self.level {
100            let event = ConfirmationEvent {
101                order_id: self.order_id,
102                previous_level: self.level,
103                new_level,
104                confirmations,
105                txid: self.txid.clone(),
106            };
107            self.previous_level = self.level;
108            self.level = new_level;
109            Some(event)
110        } else {
111            None
112        }
113    }
114
115    /// Check if payment has reached the required confirmation level
116    pub fn is_confirmed(&self, required: ConfirmationLevel) -> bool {
117        self.level.meets_threshold(required)
118    }
119}
120
121/// Event emitted when confirmation level changes
122#[derive(Debug, Clone, Serialize)]
123pub struct ConfirmationEvent {
124    /// Order ID
125    pub order_id: Uuid,
126    /// Previous confirmation level
127    pub previous_level: ConfirmationLevel,
128    /// New confirmation level
129    pub new_level: ConfirmationLevel,
130    /// Current confirmation count
131    pub confirmations: u32,
132    /// Transaction ID
133    pub txid: Option<String>,
134}
135
136impl ConfirmationEvent {
137    /// Get a notification message for this event
138    pub fn notification_message(&self) -> String {
139        match self.new_level {
140            ConfirmationLevel::Unconfirmed => {
141                format!("Payment detected in mempool for order {}", self.order_id)
142            }
143            ConfirmationLevel::OneConfirmation => {
144                format!(
145                    "Payment received first confirmation for order {} (tx: {})",
146                    self.order_id,
147                    self.txid.as_deref().unwrap_or("unknown")
148                )
149            }
150            ConfirmationLevel::ThreeConfirmations => {
151                format!(
152                    "Payment reached 3 confirmations for order {} - reasonably secure",
153                    self.order_id
154                )
155            }
156            ConfirmationLevel::SixConfirmations => {
157                format!(
158                    "Payment fully confirmed for order {} - 6+ confirmations",
159                    self.order_id
160                )
161            }
162        }
163    }
164
165    /// Check if this is a significant event worth notifying
166    pub fn is_significant(&self) -> bool {
167        // All level changes are significant
168        true
169    }
170}
171
172/// Notification types for confirmation events
173#[derive(Debug, Clone, Serialize)]
174pub enum NotificationType {
175    /// Payment detected in mempool
176    PaymentDetected,
177    /// First confirmation received
178    FirstConfirmation,
179    /// Reached reasonably secure level (3 confirmations)
180    ReasonablySecure,
181    /// Fully confirmed (6+ confirmations)
182    FullyConfirmed,
183}
184
185impl From<ConfirmationLevel> for NotificationType {
186    fn from(level: ConfirmationLevel) -> Self {
187        match level {
188            ConfirmationLevel::Unconfirmed => Self::PaymentDetected,
189            ConfirmationLevel::OneConfirmation => Self::FirstConfirmation,
190            ConfirmationLevel::ThreeConfirmations => Self::ReasonablySecure,
191            ConfirmationLevel::SixConfirmations => Self::FullyConfirmed,
192        }
193    }
194}
195
196/// Confirmation tracker that monitors multiple orders
197pub struct ConfirmationTracker {
198    /// Tracked orders by order ID
199    orders: HashMap<Uuid, TrackedOrder>,
200    /// Required confirmation level for "confirmed" status
201    required_level: ConfirmationLevel,
202}
203
204impl ConfirmationTracker {
205    /// Create a new confirmation tracker
206    pub fn new(required_level: ConfirmationLevel) -> Self {
207        Self {
208            orders: HashMap::new(),
209            required_level,
210        }
211    }
212
213    /// Create with default settings (3 confirmations required)
214    pub fn with_defaults() -> Self {
215        Self::new(ConfirmationLevel::ThreeConfirmations)
216    }
217
218    /// Start tracking an order
219    pub fn track(&mut self, order_id: Uuid, address: String, expected_amount: Amount) {
220        let order = TrackedOrder::new(order_id, address, expected_amount);
221        self.orders.insert(order_id, order);
222    }
223
224    /// Stop tracking an order
225    pub fn untrack(&mut self, order_id: Uuid) -> Option<TrackedOrder> {
226        self.orders.remove(&order_id)
227    }
228
229    /// Update confirmation count for an order
230    pub fn update(
231        &mut self,
232        order_id: Uuid,
233        confirmations: u32,
234        txid: Option<String>,
235        received_amount: Option<Amount>,
236    ) -> Option<ConfirmationEvent> {
237        if let Some(order) = self.orders.get_mut(&order_id) {
238            if let Some(txid) = txid {
239                order.txid = Some(txid);
240            }
241            if let Some(amount) = received_amount {
242                order.received_amount = Some(amount);
243            }
244            order.update_confirmations(confirmations)
245        } else {
246            None
247        }
248    }
249
250    /// Get order status
251    pub fn get(&self, order_id: Uuid) -> Option<&TrackedOrder> {
252        self.orders.get(&order_id)
253    }
254
255    /// Check if an order is confirmed
256    pub fn is_confirmed(&self, order_id: Uuid) -> bool {
257        self.orders
258            .get(&order_id)
259            .map(|o| o.is_confirmed(self.required_level))
260            .unwrap_or(false)
261    }
262
263    /// Get all orders at a specific confirmation level
264    pub fn orders_at_level(&self, level: ConfirmationLevel) -> Vec<&TrackedOrder> {
265        self.orders.values().filter(|o| o.level == level).collect()
266    }
267
268    /// Get all confirmed orders
269    pub fn confirmed_orders(&self) -> Vec<&TrackedOrder> {
270        self.orders
271            .values()
272            .filter(|o| o.is_confirmed(self.required_level))
273            .collect()
274    }
275
276    /// Get all pending orders (not yet confirmed)
277    pub fn pending_orders(&self) -> Vec<&TrackedOrder> {
278        self.orders
279            .values()
280            .filter(|o| !o.is_confirmed(self.required_level))
281            .collect()
282    }
283
284    /// Get statistics
285    pub fn stats(&self) -> ConfirmationStats {
286        let mut stats = ConfirmationStats::default();
287        for order in self.orders.values() {
288            match order.level {
289                ConfirmationLevel::Unconfirmed => stats.unconfirmed += 1,
290                ConfirmationLevel::OneConfirmation => stats.one_confirmation += 1,
291                ConfirmationLevel::ThreeConfirmations => stats.three_confirmations += 1,
292                ConfirmationLevel::SixConfirmations => stats.six_confirmations += 1,
293            }
294        }
295        stats.total = self.orders.len();
296        stats
297    }
298}
299
300/// Statistics about tracked confirmations
301#[derive(Debug, Clone, Default, Serialize)]
302pub struct ConfirmationStats {
303    /// Total tracked orders
304    pub total: usize,
305    /// Orders with 0 confirmations
306    pub unconfirmed: usize,
307    /// Orders with 1-2 confirmations
308    pub one_confirmation: usize,
309    /// Orders with 3-5 confirmations
310    pub three_confirmations: usize,
311    /// Orders with 6+ confirmations
312    pub six_confirmations: usize,
313}
314
315impl Default for ConfirmationTracker {
316    fn default() -> Self {
317        Self::with_defaults()
318    }
319}