1use std::sync::Arc;
7use tokio::sync::RwLock;
8
9use crate::activity_monitor::{
10 ActivityMonitor, ActivityMonitorConfig, SuspiciousActivity, TransactionRecord,
11};
12use crate::client::BitcoinClient;
13use crate::error::Result;
14use crate::tx_limits::{LimitConfig, LimitEnforcer};
15use crate::utxo::{
16 ConsolidationConfig, ConsolidationPlan, SelectionStrategy, UtxoManager, UtxoSelection,
17};
18
19#[derive(Debug, Clone)]
21pub struct TransactionManagerConfig {
22 pub consolidation: ConsolidationConfig,
24 pub limits: LimitConfig,
26 pub activity: ActivityMonitorConfig,
28 pub auto_consolidate: bool,
30}
31
32impl Default for TransactionManagerConfig {
33 fn default() -> Self {
34 Self {
35 consolidation: ConsolidationConfig::default(),
36 limits: LimitConfig::default(),
37 activity: ActivityMonitorConfig::default(),
38 auto_consolidate: true,
39 }
40 }
41}
42
43pub struct TransactionManager {
45 utxo_manager: UtxoManager,
46 limit_enforcer: Arc<RwLock<LimitEnforcer>>,
47 activity_monitor: Arc<ActivityMonitor>,
48 config: TransactionManagerConfig,
49}
50
51#[derive(Debug, Clone)]
53pub struct TransactionValidation {
54 pub allowed: bool,
56 pub rejection_reason: Option<String>,
58 pub suspicious_activity: Option<SuspiciousActivity>,
60 pub utxo_selection: Option<UtxoSelection>,
62}
63
64#[derive(Debug, Clone)]
66pub struct TransactionStats {
67 pub total_utxos: usize,
69 pub total_balance_sats: u64,
71 pub spendable_utxos: usize,
73 pub consolidation_recommended: bool,
75 pub consolidation_plan: Option<ConsolidationPlan>,
77}
78
79impl TransactionManager {
80 pub fn new(client: Arc<BitcoinClient>, config: TransactionManagerConfig) -> Self {
82 let utxo_manager = UtxoManager::new(client, config.consolidation.clone());
83 let limit_enforcer = Arc::new(RwLock::new(LimitEnforcer::new(config.limits.clone())));
84 let activity_monitor = Arc::new(ActivityMonitor::new(config.activity.clone()));
85
86 Self {
87 utxo_manager,
88 limit_enforcer,
89 activity_monitor,
90 config,
91 }
92 }
93
94 pub async fn validate_transaction(
96 &self,
97 user_id: &str,
98 amount_sats: u64,
99 fee_rate: f64,
100 txid: Option<String>,
101 ) -> Result<TransactionValidation> {
102 let limit_check = {
104 let enforcer = self.limit_enforcer.read().await;
105 enforcer.check_transaction(user_id, amount_sats).await
106 };
107
108 if let Err(e) = limit_check {
109 return Ok(TransactionValidation {
110 allowed: false,
111 rejection_reason: Some(format!("Limit exceeded: {}", e)),
112 suspicious_activity: None,
113 utxo_selection: None,
114 });
115 }
116
117 let activity_record = TransactionRecord {
119 txid: txid.clone().unwrap_or_else(|| "pending".to_string()),
120 user_id: user_id.to_string(),
121 amount_sats,
122 timestamp: chrono::Utc::now(),
123 };
124
125 let suspicious = self
126 .activity_monitor
127 .record_transaction(activity_record)
128 .await;
129
130 let utxo_selection = if amount_sats > 0 {
132 match self.utxo_manager.select_utxos(
133 amount_sats,
134 fee_rate,
135 SelectionStrategy::BranchAndBound,
136 ) {
137 Ok(selection) => Some(selection),
138 Err(e) => {
139 return Ok(TransactionValidation {
140 allowed: false,
141 rejection_reason: Some(format!("UTXO selection failed: {}", e)),
142 suspicious_activity: suspicious,
143 utxo_selection: None,
144 });
145 }
146 }
147 } else {
148 None
149 };
150
151 Ok(TransactionValidation {
152 allowed: true,
153 rejection_reason: None,
154 suspicious_activity: suspicious,
155 utxo_selection,
156 })
157 }
158
159 pub async fn record_completed_transaction(
161 &self,
162 user_id: &str,
163 amount_sats: u64,
164 txid: String,
165 ) {
166 let enforcer = self.limit_enforcer.write().await;
167 enforcer
168 .record_transaction(user_id, amount_sats, Some(txid))
169 .await;
170 }
171
172 pub fn get_statistics(&self) -> Result<TransactionStats> {
174 let utxos = self.utxo_manager.list_utxos()?;
175
176 let total_balance_sats: u64 = utxos.iter().map(|u| u.amount_sats).sum();
177 let spendable_utxos = utxos.iter().filter(|u| u.spendable && u.safe).count();
178
179 let consolidation_recommended = self.utxo_manager.should_consolidate()?;
180 let consolidation_plan = if consolidation_recommended {
181 self.utxo_manager.get_consolidation_plan()?
182 } else {
183 None
184 };
185
186 Ok(TransactionStats {
187 total_utxos: utxos.len(),
188 total_balance_sats,
189 spendable_utxos,
190 consolidation_recommended,
191 consolidation_plan,
192 })
193 }
194
195 pub async fn get_user_usage(
197 &self,
198 user_id: &str,
199 period: crate::tx_limits::LimitPeriod,
200 ) -> (u64, u32) {
201 let enforcer = self.limit_enforcer.read().await;
202 enforcer.get_user_usage(user_id, period).await
203 }
204
205 pub async fn get_platform_usage(&self, period: crate::tx_limits::LimitPeriod) -> (u64, u32) {
207 let enforcer = self.limit_enforcer.read().await;
208 enforcer.get_platform_usage(period).await
209 }
210
211 pub fn subscribe_to_alerts(&self) -> tokio::sync::broadcast::Receiver<SuspiciousActivity> {
213 self.activity_monitor.subscribe()
214 }
215
216 pub async fn should_consolidate_now(&self) -> Result<bool> {
218 if !self.config.auto_consolidate {
219 return Ok(false);
220 }
221
222 let stats = self.get_statistics()?;
223 Ok(stats.consolidation_recommended)
224 }
225
226 pub fn get_consolidation_plan(&self) -> Result<Option<ConsolidationPlan>> {
228 self.utxo_manager.get_consolidation_plan()
229 }
230
231 pub fn activity_monitor(&self) -> &ActivityMonitor {
233 &self.activity_monitor
234 }
235
236 pub fn utxo_manager(&self) -> &UtxoManager {
238 &self.utxo_manager
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn test_transaction_manager_config_defaults() {
248 let config = TransactionManagerConfig::default();
249 assert!(config.auto_consolidate);
250 assert_eq!(config.consolidation.min_utxo_count, 50);
251 assert_eq!(config.limits.single_tx_max_sats, 50_000_000);
252 assert_eq!(config.activity.large_tx_threshold_sats, 10_000_000);
253 }
254
255 #[test]
256 fn test_transaction_validation_result() {
257 let validation = TransactionValidation {
258 allowed: true,
259 rejection_reason: None,
260 suspicious_activity: None,
261 utxo_selection: None,
262 };
263
264 assert!(validation.allowed);
265 assert!(validation.rejection_reason.is_none());
266 }
267}