anya_core/bitcoin/wallet/
mod.rs

1// Bitcoin Wallet Module
2// Implements unified wallet capabilities for Bitcoin and related chains
3//
4// [AIR-3][AIS-3][AIT-3][AIM-2][AIP-3][BPC-3][RES-2][SCL-2]
5// This module provides comprehensive wallet functionality with high security,
6// privacy, and protocol compliance ratings.
7
8use crate::bitcoin::error::BitcoinError;
9use crate::bitcoin::interface::BitcoinInterface;
10use crate::{AnyaError, AnyaResult};
11use async_trait::async_trait;
12use bitcoin::absolute::LockTime;
13use bitcoin::bip32::DerivationPath;
14use bitcoin::hashes::Hash;
15use bitcoin::psbt::Psbt as PSBT;
16use bitcoin::secp256k1::{Secp256k1, SecretKey};
17use bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid};
18use bitcoin::{Amount, ScriptBuf};
19use log::error;
20use serde::{Deserialize, Serialize};
21use std::collections::HashMap;
22use std::path::{Path, PathBuf};
23use std::str::FromStr;
24use std::sync::{Arc, Mutex};
25use thiserror::Error;
26
27pub mod bip32;
28pub mod transactions;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum WalletType {
32    Standard,         // Basic Bitcoin wallet
33    Taproot,          // Bitcoin with Taproot support
34    LightningEnabled, // Bitcoin with Lightning support
35    MultiChain,       // Support for multiple chains
36}
37
38pub struct WalletConfig {
39    pub wallet_type: WalletType,
40    pub network: Network,
41    pub name: String,
42    pub seed_phrase: Option<String>,
43    pub password: Option<String>,
44    pub receive_descriptor: String,
45    pub change_descriptor: String,
46    pub xpub: Option<String>,
47    pub data_dir: PathBuf,
48    pub use_rpc: bool,
49    pub coin_selection: CoinSelectionStrategy,
50    pub gap_limit: u32,
51    pub min_confirmations: u32,
52    pub fee_strategy: FeeStrategy,
53}
54
55pub trait KeyManager {
56    fn derive_key(&self, path: &str) -> AnyaResult<SecretKey>;
57    fn get_public_key(&self, path: &str) -> AnyaResult<bitcoin::secp256k1::PublicKey>;
58    fn sign_message(&self, message: &[u8], path: &str) -> AnyaResult<Vec<u8>>;
59    fn verify_message(&self, message: &[u8], _signature: &[u8], path: &str) -> AnyaResult<bool>;
60}
61
62pub trait AddressManager {
63    fn get_new_address(&self, address_type: AddressType) -> AnyaResult<Address>;
64    fn get_address(&self, index: u32, address_type: AddressType) -> AnyaResult<Address>;
65    fn is_address_mine(&self, address: &str) -> AnyaResult<bool>;
66    fn get_all_addresses(&self) -> AnyaResult<Vec<Address>>;
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub enum AddressType {
71    Legacy,       // P2PKH
72    SegWit,       // P2WPKH
73    NestedSegWit, // P2SH-P2WPKH
74    Taproot,      // P2TR
75}
76
77pub trait TransactionManager {
78    fn create_transaction(
79        &self,
80        outputs: Vec<(String, u64)>,
81        _fee_rate: f64,
82        _options: transactions::TxOptions,
83    ) -> AnyaResult<Transaction>;
84
85    fn sign_transaction(&self, tx: &mut Transaction) -> AnyaResult<()>;
86    fn broadcast_transaction(&self, tx: &Transaction) -> AnyaResult<String>;
87    fn get_transaction(&self, txid: &str) -> AnyaResult<Option<Transaction>>;
88    fn get_transactions(&self, limit: usize, offset: usize) -> AnyaResult<Vec<Transaction>>;
89}
90
91pub trait BalanceManager {
92    fn get_balance(&self) -> AnyaResult<u64>;
93    fn get_unconfirmed_balance(&self) -> AnyaResult<u64>;
94    fn get_asset_balance(&self, asset_id: &str) -> AnyaResult<u64>;
95    fn get_all_asset_balances(&self) -> AnyaResult<HashMap<String, u64>>;
96}
97
98pub trait UnifiedWallet: KeyManager + AddressManager + TransactionManager + BalanceManager {
99    fn name(&self) -> &str;
100    fn wallet_type(&self) -> WalletType;
101    fn network(&self) -> Network;
102
103    // Chain-specific operations
104    fn get_stacks_address(&self) -> AnyaResult<String>;
105    fn get_rsk_address(&self) -> AnyaResult<String>;
106    fn get_liquid_address(&self) -> AnyaResult<String>;
107
108    // Asset management
109    fn add_asset(&self, asset_id: &str, name: &str, asset_type: &str) -> AnyaResult<()>;
110    fn remove_asset(&self, asset_id: &str) -> AnyaResult<()>;
111    fn get_assets(&self) -> AnyaResult<Vec<Asset>>;
112
113    // Key export/import
114    fn export_xpriv(&self, password: &str) -> AnyaResult<String>;
115    fn import_xpriv(&self, xpriv: &str, password: &str) -> AnyaResult<()>;
116
117    // Backup management
118    fn backup(&self, path: &str, password: &str) -> AnyaResult<()>;
119    fn restore(&self, path: &str, password: &str) -> AnyaResult<()>;
120}
121
122#[derive(Clone)]
123pub struct Asset {
124    pub id: String,
125    pub name: String,
126    pub asset_type: String,
127    pub chain: String,
128    pub balance: u64,
129    pub metadata: HashMap<String, String>,
130}
131
132#[allow(dead_code)]
133pub struct Wallet {
134    config: WalletConfig,
135    seed: Mutex<Option<[u8; 64]>>,
136    secp: Secp256k1<bitcoin::secp256k1::All>,
137    addresses: Mutex<HashMap<AddressType, Vec<Address>>>,
138    assets: Mutex<HashMap<String, Asset>>,
139    transactions: Mutex<Vec<Transaction>>,
140    bitcoin_client: Option<Arc<dyn BitcoinInterface>>,
141}
142
143impl Wallet {
144    pub fn new(config: WalletConfig, bitcoin_client: Option<Arc<dyn BitcoinInterface>>) -> Self {
145        Self {
146            config,
147            seed: Mutex::new(None),
148            secp: Secp256k1::new(),
149            addresses: Mutex::new(HashMap::new()),
150            assets: Mutex::new(HashMap::new()),
151            transactions: Mutex::new(Vec::new()),
152            bitcoin_client,
153        }
154    }
155
156    pub fn initialize(&self, seed_phrase: Option<&str>, password: Option<&str>) -> AnyaResult<()> {
157        // Generate or recover seed
158        let seed = if let Some(phrase) = seed_phrase {
159            bip32::seed_from_mnemonic(phrase, password.unwrap_or(""))?
160        } else {
161            bip32::generate_seed(password.unwrap_or(""))?
162        };
163
164        let mut seed_guard = self
165            .seed
166            .lock()
167            .map_err(|e| format!("Mutex lock error: {e}"))?;
168        *seed_guard = Some(seed);
169
170        // Generate initial addresses
171        self.init_addresses()?;
172
173        Ok(())
174    }
175
176    fn init_addresses(&self) -> AnyaResult<()> {
177        let mut addresses = self
178            .addresses
179            .lock()
180            .map_err(|e| format!("Mutex lock error: {e}"))?;
181
182        // Generate 20 addresses of each type
183        for address_type in [
184            AddressType::Legacy,
185            AddressType::SegWit,
186            AddressType::NestedSegWit,
187            AddressType::Taproot,
188        ]
189        .iter()
190        {
191            let mut type_addresses = Vec::new();
192
193            for i in 0..20 {
194                let path = match address_type {
195                    AddressType::Legacy => format!("m/44'/0'/0'/0/{i}"),
196                    AddressType::SegWit => format!("m/84'/0'/0'/0/{i}"),
197                    AddressType::NestedSegWit => format!("m/49'/0'/0'/0/{i}"),
198                    AddressType::Taproot => format!("m/86'/0'/0'/0/{i}"),
199                };
200
201                let secret_key = self.derive_key(&path)?;
202                let public_key =
203                    bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
204
205                // Convert to bitcoin::PublicKey
206                let bitcoin_pubkey = bitcoin::PublicKey::new(public_key);
207                // Get compressed public key for p2wpkh and p2shwpkh
208                let compressed_pubkey = bitcoin::key::CompressedPublicKey::from_slice(
209                    &bitcoin_pubkey.inner.serialize(),
210                )?;
211
212                let address = match address_type {
213                    AddressType::Legacy => Address::p2pkh(bitcoin_pubkey, self.config.network),
214                    AddressType::SegWit => Address::p2wpkh(&compressed_pubkey, self.config.network),
215                    AddressType::NestedSegWit => {
216                        Address::p2shwpkh(&compressed_pubkey, self.config.network)
217                    }
218                    AddressType::Taproot => {
219                        let xonly = bitcoin::secp256k1::XOnlyPublicKey::from(public_key);
220                        Address::p2tr(&self.secp, xonly, None, self.config.network)
221                    }
222                };
223
224                type_addresses.push(address);
225            }
226
227            addresses.insert(*address_type, type_addresses);
228        }
229
230        Ok(())
231    }
232}
233
234impl KeyManager for Wallet {
235    fn derive_key(&self, path: &str) -> AnyaResult<SecretKey> {
236        let seed_guard = self
237            .seed
238            .lock()
239            .map_err(|e| format!("Mutex lock error: {e}"))?;
240        let seed = seed_guard
241            .as_ref()
242            .ok_or_else(|| BitcoinError::Wallet("Wallet not initialized".to_string()))?;
243
244        bip32::derive_key_from_seed(seed, path).map_err(|e| AnyaError::Bitcoin(e.to_string()))
245    }
246
247    fn get_public_key(&self, path: &str) -> AnyaResult<bitcoin::secp256k1::PublicKey> {
248        let private_key = self.derive_key(path)?;
249        let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &private_key);
250        Ok(public_key)
251    }
252
253    fn sign_message(&self, message: &[u8], path: &str) -> AnyaResult<Vec<u8>> {
254        let private_key = self.derive_key(path)?;
255
256        // Hash the message with SHA256
257        let hash = bitcoin::hashes::sha256::Hash::hash(message);
258        let message_hash = bitcoin::secp256k1::Message::from_digest(hash.to_byte_array());
259
260        let signature = self.secp.sign_ecdsa(&message_hash, &private_key);
261        Ok(signature.serialize_der().to_vec())
262    }
263
264    fn verify_message(&self, message: &[u8], signature: &[u8], path: &str) -> AnyaResult<bool> {
265        let public_key = self.get_public_key(path)?;
266
267        // Hash the message with SHA256
268        let hash = bitcoin::hashes::sha256::Hash::hash(message);
269        let message_hash = bitcoin::secp256k1::Message::from_digest(hash.to_byte_array());
270
271        let signature = bitcoin::secp256k1::ecdsa::Signature::from_der(signature)
272            .map_err(|e| BitcoinError::Wallet(format!("Invalid signature: {e}")))?;
273
274        Ok(self
275            .secp
276            .verify_ecdsa(&message_hash, &signature, &public_key)
277            .is_ok())
278    }
279}
280
281impl AddressManager for Wallet {
282    fn get_new_address(&self, address_type: AddressType) -> AnyaResult<Address> {
283        let mut addresses = self
284            .addresses
285            .lock()
286            .map_err(|e| format!("Mutex lock error: {e}"))?;
287
288        let type_addresses = addresses.entry(address_type).or_insert_with(Vec::new);
289
290        let index = type_addresses.len() as u32;
291
292        let path = match address_type {
293            AddressType::Legacy => format!("m/44'/0'/0'/0/{index}"),
294            AddressType::SegWit => format!("m/84'/0'/0'/0/{index}"),
295            AddressType::NestedSegWit => format!("m/49'/0'/0'/0/{index}"),
296            AddressType::Taproot => format!("m/86'/0'/0'/0/{index}"),
297        };
298
299        let secret_key = self.derive_key(&path)?;
300        let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
301
302        // Convert to bitcoin::PublicKey
303        let bitcoin_pubkey = bitcoin::PublicKey::new(public_key);
304        // Get compressed public key for p2wpkh and p2shwpkh
305        let compressed_pubkey =
306            bitcoin::key::CompressedPublicKey::from_slice(&bitcoin_pubkey.inner.serialize())?;
307
308        let address = match address_type {
309            AddressType::Legacy => Address::p2pkh(bitcoin_pubkey, self.config.network),
310            AddressType::SegWit => Address::p2wpkh(&compressed_pubkey, self.config.network),
311            AddressType::NestedSegWit => Address::p2shwpkh(&compressed_pubkey, self.config.network),
312            AddressType::Taproot => {
313                let xonly = bitcoin::secp256k1::XOnlyPublicKey::from(public_key);
314                Address::p2tr(&self.secp, xonly, None, self.config.network)
315            }
316        };
317
318        type_addresses.push(address.clone());
319
320        Ok(address)
321    }
322
323    fn get_address(&self, index: u32, address_type: AddressType) -> AnyaResult<Address> {
324        let addresses = self
325            .addresses
326            .lock()
327            .map_err(|e| format!("Mutex lock error: {e}"))?;
328
329        if let Some(type_addresses) = addresses.get(&address_type) {
330            if let Some(address) = type_addresses.get(index as usize) {
331                return Ok(address.clone());
332            }
333        }
334
335        // Address not found, derive it
336        let path = match address_type {
337            AddressType::Legacy => format!("m/44'/0'/0'/0/{index}"),
338            AddressType::SegWit => format!("m/84'/0'/0'/0/{index}"),
339            AddressType::NestedSegWit => format!("m/49'/0'/0'/0/{index}"),
340            AddressType::Taproot => format!("m/86'/0'/0'/0/{index}"),
341        };
342
343        let secret_key = self.derive_key(&path)?;
344        let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
345
346        // Convert to bitcoin::PublicKey
347        let bitcoin_pubkey = bitcoin::PublicKey::new(public_key);
348        // Get compressed public key for p2wpkh and p2shwpkh
349        let compressed_pubkey =
350            bitcoin::key::CompressedPublicKey::from_slice(&bitcoin_pubkey.inner.serialize())?;
351
352        let address = match address_type {
353            AddressType::Legacy => Address::p2pkh(bitcoin_pubkey, self.config.network),
354            AddressType::SegWit => Address::p2wpkh(&compressed_pubkey, self.config.network),
355            AddressType::NestedSegWit => Address::p2shwpkh(&compressed_pubkey, self.config.network),
356            AddressType::Taproot => {
357                let xonly = bitcoin::secp256k1::XOnlyPublicKey::from(public_key);
358                Address::p2tr(&self.secp, xonly, None, self.config.network)
359            }
360        };
361
362        Ok(address)
363    }
364
365    fn is_address_mine(&self, address: &str) -> AnyaResult<bool> {
366        let addresses = self
367            .addresses
368            .lock()
369            .map_err(|e| format!("Mutex lock error: {e}"))?;
370
371        for type_addresses in addresses.values() {
372            for addr in type_addresses {
373                if addr.to_string() == address {
374                    return Ok(true);
375                }
376            }
377        }
378
379        Ok(false)
380    }
381
382    fn get_all_addresses(&self) -> AnyaResult<Vec<Address>> {
383        let addresses = self
384            .addresses
385            .lock()
386            .map_err(|e| format!("Mutex lock error: {e}"))?;
387
388        let mut result = Vec::new();
389        for type_addresses in addresses.values() {
390            result.extend(type_addresses.clone());
391        }
392
393        Ok(result)
394    }
395}
396
397impl TransactionManager for Wallet {
398    fn create_transaction(
399        &self,
400        outputs: Vec<(String, u64)>,
401        _fee_rate: f64,
402        _options: transactions::TxOptions,
403    ) -> AnyaResult<Transaction> {
404        // Simplified implementation
405        let mut tx_outs = Vec::new();
406
407        for (addr, amount) in outputs {
408            let script_pubkey = Address::from_str(&addr)
409                .map_err(|e| BitcoinError::Wallet(format!("Invalid address: {e}")))?
410                .require_network(self.config.network)
411                .map_err(|e| BitcoinError::Wallet(format!("Network mismatch: {e}")))?
412                .script_pubkey();
413
414            tx_outs.push(TxOut {
415                value: Amount::from_sat(amount),
416                script_pubkey,
417            });
418        }
419
420        // In a real implementation, we would select UTXOs, create inputs, etc.
421        // For simplicity, we're returning a dummy transaction
422        Ok(Transaction {
423            version: bitcoin::transaction::Version(2),
424            lock_time: LockTime::ZERO,
425            input: vec![],
426            output: tx_outs,
427        })
428    }
429
430    fn sign_transaction(&self, _tx: &mut Transaction) -> AnyaResult<()> {
431        // Simplified implementation
432        Ok(())
433    }
434
435    fn broadcast_transaction(&self, tx: &Transaction) -> AnyaResult<String> {
436        // Simplified implementation
437        Ok(tx.compute_txid().to_string())
438    }
439
440    fn get_transaction(&self, _txid: &str) -> AnyaResult<Option<Transaction>> {
441        // Simplified implementation
442        Ok(None)
443    }
444
445    fn get_transactions(&self, _limit: usize, _offset: usize) -> AnyaResult<Vec<Transaction>> {
446        // Simplified implementation
447        Ok(vec![])
448    }
449}
450
451impl BalanceManager for Wallet {
452    fn get_balance(&self) -> AnyaResult<u64> {
453        // Simplified implementation
454        Ok(0)
455    }
456
457    fn get_unconfirmed_balance(&self) -> AnyaResult<u64> {
458        // Simplified implementation
459        Ok(0)
460    }
461
462    fn get_asset_balance(&self, asset_id: &str) -> AnyaResult<u64> {
463        let assets = self
464            .assets
465            .lock()
466            .map_err(|e| format!("Mutex lock error: {e}"))?;
467
468        if let Some(asset) = assets.get(asset_id) {
469            Ok(asset.balance)
470        } else {
471            Err(BitcoinError::Wallet(format!("Asset not found: {asset_id}")).into())
472        }
473    }
474
475    fn get_all_asset_balances(&self) -> AnyaResult<HashMap<String, u64>> {
476        let assets = self
477            .assets
478            .lock()
479            .map_err(|e| format!("Mutex lock error: {e}"))?;
480
481        let mut balances = HashMap::new();
482        for (id, asset) in assets.iter() {
483            balances.insert(id.clone(), asset.balance);
484        }
485
486        Ok(balances)
487    }
488}
489
490impl UnifiedWallet for Wallet {
491    fn name(&self) -> &str {
492        &self.config.name
493    }
494
495    fn wallet_type(&self) -> WalletType {
496        self.config.wallet_type.clone()
497    }
498
499    fn network(&self) -> Network {
500        self.config.network
501    }
502
503    fn get_stacks_address(&self) -> AnyaResult<String> {
504        // Derive a Stacks address from the same seed
505        let secret_key = self.derive_key("m/44'/5757'/0'/0/0")?;
506
507        // Convert the key to a Stacks address format
508        // Note: This is a simplified implementation. Production would use proper Stacks address derivation
509        let address_hash = format!(
510            "{:x}",
511            secret_key.secret_bytes()[0..20]
512                .iter()
513                .fold(0u64, |acc, &b| acc.wrapping_mul(256).wrapping_add(b as u64))
514        );
515        Ok(format!("ST{}", &address_hash[..32].to_uppercase()))
516    }
517
518    fn get_rsk_address(&self) -> AnyaResult<String> {
519        // Derive an RSK address from the same seed
520        let secret_key = self.derive_key("m/44'/137'/0'/0/0")?;
521
522        // Convert the key to an RSK address format (Ethereum-style)
523        // Note: This is a simplified implementation. Production would use proper RSK address derivation
524        let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
525        let address_bytes = &public_key.serialize()[1..]; // Remove 0x04 prefix
526        let address_hash = format!(
527            "{:02x}",
528            address_bytes[0..20]
529                .iter()
530                .fold(0u64, |acc, &b| acc.wrapping_mul(256).wrapping_add(b as u64))
531        );
532        Ok(format!("0x{}", &address_hash[..40]))
533    }
534
535    fn get_liquid_address(&self) -> AnyaResult<String> {
536        // Derive a Liquid address from the same seed
537        let secret_key = self.derive_key("m/44'/2'/0'/0/0")?;
538
539        // Convert the key to a Liquid address format (Elements/Confidential addresses)
540        // Note: This is a simplified implementation. Production would use proper Liquid address derivation
541        let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
542        let address_bytes = &public_key.serialize()[1..]; // Remove 0x04 prefix
543        let address_hash = format!(
544            "{:02x}",
545            address_bytes[0..25].iter().fold(0u128, |acc, &b| acc
546                .wrapping_mul(256)
547                .wrapping_add(b as u128))
548        );
549        Ok(format!("VT{}", &address_hash[..50]))
550    }
551
552    fn add_asset(&self, asset_id: &str, name: &str, asset_type: &str) -> AnyaResult<()> {
553        let mut assets = self
554            .assets
555            .lock()
556            .map_err(|e| format!("Mutex lock error: {e}"))?;
557
558        if assets.contains_key(asset_id) {
559            return Err(BitcoinError::Wallet(format!("Asset already exists: {asset_id}")).into());
560        }
561
562        let asset = Asset {
563            id: asset_id.to_string(),
564            name: name.to_string(),
565            asset_type: asset_type.to_string(),
566            chain: determine_chain_from_asset_id(asset_id),
567            balance: 0,
568            metadata: HashMap::new(),
569        };
570
571        assets.insert(asset_id.to_string(), asset);
572
573        Ok(())
574    }
575
576    fn remove_asset(&self, asset_id: &str) -> AnyaResult<()> {
577        let mut assets = self
578            .assets
579            .lock()
580            .map_err(|e| format!("Mutex lock error: {e}"))?;
581
582        if assets.remove(asset_id).is_none() {
583            return Err(BitcoinError::Wallet(format!("Asset not found: {asset_id}")).into());
584        }
585
586        Ok(())
587    }
588
589    fn get_assets(&self) -> AnyaResult<Vec<Asset>> {
590        let assets = self
591            .assets
592            .lock()
593            .map_err(|e| format!("Mutex lock error: {e}"))?;
594        Ok(assets.values().cloned().collect())
595    }
596
597    fn export_xpriv(&self, _password: &str) -> AnyaResult<String> {
598        // Simplified implementation
599        Err(BitcoinError::Wallet("Not implemented".to_string()).into())
600    }
601
602    fn import_xpriv(&self, _xpriv: &str, _password: &str) -> AnyaResult<()> {
603        // Simplified implementation
604        Err(BitcoinError::Wallet("Not implemented".to_string()).into())
605    }
606
607    fn backup(&self, _path: &str, _password: &str) -> AnyaResult<()> {
608        // Simplified implementation
609        Err(BitcoinError::Wallet("Not implemented".to_string()).into())
610    }
611
612    fn restore(&self, _path: &str, _password: &str) -> AnyaResult<()> {
613        // Simplified implementation
614        Err(BitcoinError::Wallet("Not implemented".to_string()).into())
615    }
616}
617
618// Helper function to determine chain from asset ID
619fn determine_chain_from_asset_id(asset_id: &str) -> String {
620    // Simple heuristic based on asset ID prefix
621    if asset_id.starts_with("btc-") {
622        "Bitcoin".to_string()
623    } else if asset_id.starts_with("lq-") {
624        "Liquid".to_string()
625    } else if asset_id.starts_with("rsk-") {
626        "RSK".to_string()
627    } else {
628        "Unknown".to_string()
629    }
630}
631
632/// Wallet error type
633#[derive(Error, Debug)]
634pub enum WalletError {
635    /// Error related to the Bitcoin library
636    #[error("Bitcoin error: {0}")]
637    BitcoinError(String),
638
639    /// Error related to secp256k1
640    #[error("Secp256k1 error: {0}")]
641    Secp256k1Error(#[from] secp256k1::Error),
642
643    /// Error related to the BIP39 library
644    #[error("BIP39 error: {0}")]
645    Bip39Error(String),
646
647    /// Error related to descriptors
648    #[error("Descriptor error: {0}")]
649    DescriptorError(String),
650
651    /// Error related to wallet storage
652    #[error("Wallet storage error: {0}")]
653    StorageError(String),
654
655    /// Error related to wallet configuration
656    #[error("Wallet configuration error: {0}")]
657    ConfigError(String),
658
659    /// Error related to transaction creation
660    #[error("Transaction creation error: {0}")]
661    TransactionError(String),
662
663    /// Error related to PSBT operations
664    #[error("PSBT error: {0}")]
665    PsbtError(String),
666
667    /// Error related to signing operations
668    #[error("Signing error: {0}")]
669    SigningError(String),
670
671    /// Error related to blockchain synchronization
672    #[error("Synchronization error: {0}")]
673    SyncError(String),
674
675    /// Error related to address generation
676    #[error("Address generation error: {0}")]
677    AddressError(String),
678
679    /// Error related to fee estimation
680    #[error("Fee estimation error: {0}")]
681    FeeEstimationError(String),
682
683    /// RPC error
684    #[error("RPC error: {0}")]
685    RpcError(String),
686
687    /// Invalid parameters
688    #[error("Invalid parameters: {0}")]
689    InvalidParameters(String),
690
691    /// Insufficient funds
692    #[error("Insufficient funds: {0}")]
693    InsufficientFunds(String),
694
695    /// IO error
696    #[error("IO error: {0}")]
697    IoError(#[from] std::io::Error),
698
699    /// Serialization error
700    #[error("Serialization error: {0}")]
701    SerializationError(String),
702
703    /// UTXO management error
704    #[error("UTXO management error: {0}")]
705    UtxoError(String),
706}
707
708/// UTXO (Unspent Transaction Output) representation
709#[derive(Debug, Clone, Serialize, Deserialize)]
710pub struct Utxo {
711    /// The outpoint of this UTXO
712    pub outpoint: OutPoint,
713
714    /// The TxOut data
715    pub txout: TxOut,
716
717    /// The redeem script (if available)
718    pub redeem_script: Option<ScriptBuf>,
719
720    /// The witness script (if available)
721    pub witness_script: Option<ScriptBuf>,
722
723    /// Confirmations (0 for unconfirmed)
724    pub confirmations: u32,
725
726    /// Is this UTXO spendable (not locked or reserved)
727    pub spendable: bool,
728
729    /// Is this UTXO coming from the wallet (vs a watch-only address)
730    pub from_wallet: bool,
731}
732
733/// Transaction information
734#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct TransactionInfo {
736    /// Transaction ID
737    pub txid: Txid,
738
739    /// Complete transaction
740    pub transaction: Transaction,
741
742    /// Block height (None if unconfirmed)
743    pub block_height: Option<u32>,
744
745    /// Confirmations (0 for unconfirmed)
746    pub confirmations: u32,
747
748    /// Fee in satoshis
749    pub fee: Option<u64>,
750
751    /// Transaction time (from block)
752    pub timestamp: Option<u64>,
753
754    /// Our inputs value (sum of wallet inputs)
755    pub sent: u64,
756
757    /// Our outputs value (sum of wallet outputs)
758    pub received: u64,
759
760    /// Labels associated with this transaction
761    pub labels: Vec<String>,
762}
763
764/// Fee rate type
765#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
766pub enum FeeRate {
767    /// Satoshis per kilobyte
768    SatPerKb(u64),
769
770    /// Satoshis per virtual byte
771    SatPerVb(u64),
772}
773
774impl FeeRate {
775    /// Convert to satoshis per virtual byte
776    pub fn to_sat_per_vb(&self) -> u64 {
777        match self {
778            FeeRate::SatPerKb(fee) => (fee + 999) / 1000,
779            FeeRate::SatPerVb(fee) => *fee,
780        }
781    }
782
783    /// Convert to satoshis per kilobyte
784    pub fn to_sat_per_kb(&self) -> u64 {
785        match self {
786            FeeRate::SatPerKb(fee) => *fee,
787            FeeRate::SatPerVb(fee) => fee * 1000,
788        }
789    }
790}
791
792/// Wallet synchronization state
793#[derive(Debug, Clone, Serialize, Deserialize)]
794pub struct SyncState {
795    /// Latest known block height
796    pub block_height: u32,
797
798    /// Latest known block hash
799    pub block_hash: String,
800
801    /// Latest scan time
802    pub last_scan: u64,
803
804    /// Sync progress (0.0 to 1.0)
805    pub progress: f64,
806
807    /// Is initial block download still in progress
808    pub ibd: bool,
809}
810
811/// Wallet trait definition
812#[async_trait]
813pub trait WalletTrait: Send + Sync {
814    /// Initialize the wallet
815    async fn init(&self) -> Result<(), WalletError>;
816
817    /// Generate a new receiving address
818    async fn get_new_address(&self) -> Result<Address, WalletError>;
819
820    /// Get the current receiving address (without incrementing)
821    async fn get_current_address(&self) -> Result<Address, WalletError>;
822
823    /// Get a change address
824    async fn get_change_address(&self) -> Result<Address, WalletError>;
825
826    /// Check if an address belongs to this wallet
827    async fn is_mine(&self, address: &Address) -> Result<bool, WalletError>;
828
829    /// Get all wallet addresses
830    async fn list_addresses(&self) -> Result<Vec<Address>, WalletError>;
831
832    /// Get wallet balance
833    async fn get_balance(&self) -> Result<u64, WalletError>;
834
835    /// Get wallet balance with details
836    async fn get_detailed_balance(&self) -> Result<(u64, u64, u64), WalletError>;
837
838    /// List unspent UTXOs
839    async fn list_utxos(&self) -> Result<Vec<Utxo>, WalletError>;
840
841    /// Get transaction history
842    async fn get_transactions(&self) -> Result<Vec<TransactionInfo>, WalletError>;
843
844    /// Get transaction by ID
845    async fn get_transaction(&self, txid: &Txid) -> Result<Option<TransactionInfo>, WalletError>;
846
847    /// Create a transaction
848    async fn create_transaction(&self, params: TransactionParams) -> Result<PSBT, WalletError>;
849
850    /// Sign a transaction
851    async fn sign_transaction(&self, psbt: &mut PSBT) -> Result<bool, WalletError>;
852
853    /// Broadcast a transaction
854    async fn broadcast_transaction(&self, transaction: &Transaction) -> Result<Txid, WalletError>;
855
856    /// Get fee estimate for the given strategy
857    async fn get_fee_rate(&self, strategy: FeeStrategy) -> Result<FeeRate, WalletError>;
858
859    /// Calculate fee for a transaction
860    async fn calculate_fee(&self, psbt: &PSBT) -> Result<u64, WalletError>;
861
862    /// Synchronize the wallet with the blockchain
863    async fn sync(&self) -> Result<SyncState, WalletError>;
864
865    /// Export wallet data
866    async fn export(&self, path: &Path) -> Result<(), WalletError>;
867
868    /// Import wallet data
869    async fn import(&self, path: &Path) -> Result<(), WalletError>;
870
871    /// Create backup
872    async fn backup(&self, path: &Path) -> Result<(), WalletError>;
873
874    /// Get wallet information
875    async fn get_info(&self) -> Result<WalletInfo, WalletError>;
876}
877
878/// Wallet info
879#[derive(Debug, Clone, Serialize, Deserialize)]
880pub struct WalletInfo {
881    /// Wallet name
882    pub name: String,
883
884    /// Wallet version
885    pub version: String,
886
887    /// Wallet format
888    pub format: String,
889
890    /// Network
891    pub network: Network,
892
893    /// Current balance
894    pub balance: u64,
895
896    /// Unconfirmed balance
897    pub unconfirmed_balance: u64,
898
899    /// Immature balance
900    pub immature_balance: u64,
901
902    /// Number of keys
903    pub keypools: u32,
904
905    /// Number of transactions
906    pub tx_count: u32,
907
908    /// Keypool oldest
909    pub keypool_oldest: u64,
910
911    /// Keypool size
912    pub keypool_size: u32,
913
914    /// Payee requires witness
915    pub private_keys_enabled: bool,
916
917    /// Unlocked until
918    pub unlocked_until: Option<u64>,
919
920    /// HD seed version
921    pub hdseedid: Option<String>,
922
923    /// Is the wallet avoiding reuse
924    pub avoid_reuse: bool,
925
926    /// Scanning status
927    pub scanning: bool,
928
929    /// Descriptors enabled
930    pub descriptors: bool,
931}
932
933/// Bitcoin wallet implementation
934pub struct BitcoinWallet {
935    /// Wallet configuration
936    #[allow(dead_code)]
937    // Required for future wallet extensibility and compliance (see docs/INDEX_CORRECTED.md)
938    config: WalletConfig,
939
940    /// Wallet data storage
941    #[allow(dead_code)] // Required for future storage backends (see docs/INDEX_CORRECTED.md)
942    storage: Arc<Mutex<WalletStorage>>,
943
944    /// Secp256k1 context
945    #[allow(dead_code)]
946    // Required for future cryptographic operations (see docs/research/PROTOCOL_UPGRADES.md)
947    secp: Secp256k1<bitcoin::secp256k1::All>,
948}
949
950/// Wallet storage structure
951#[derive(Debug, Serialize, Deserialize)]
952struct WalletStorage {
953    /// Wallet metadata
954    metadata: WalletMetadata,
955
956    /// UTXOs
957    utxos: HashMap<OutPoint, Utxo>,
958
959    /// Transactions
960    transactions: HashMap<Txid, TransactionInfo>,
961
962    /// Address index mapping
963    addresses: HashMap<String, AddressInfo>,
964
965    /// Current indexes
966    indexes: WalletIndexes,
967}
968
969/// Wallet metadata
970#[derive(Debug, Clone, Serialize, Deserialize)]
971struct WalletMetadata {
972    /// Wallet creation time
973    created_at: u64,
974
975    /// Wallet last updated
976    updated_at: u64,
977
978    /// Wallet version
979    version: String,
980
981    /// Wallet network
982    network: Network,
983
984    /// Wallet master fingerprint
985    master_fingerprint: Option<[u8; 4]>,
986
987    /// Labels
988    labels: HashMap<String, String>,
989}
990
991/// Wallet address information
992#[derive(Debug, Clone, Serialize, Deserialize)]
993pub struct AddressInfo {
994    /// The address string
995    pub address: String,
996    /// The path from which this address was derived
997    path: Option<DerivationPath>,
998    /// The script
999    script: ScriptBuf,
1000    /// Is this a change address
1001    is_change: bool,
1002    /// Index in the derivation sequence
1003    index: u32,
1004    /// The address labels
1005    labels: Vec<String>,
1006    /// Last time this address was used
1007    last_used: Option<u64>,
1008}
1009
1010/// Wallet index tracking
1011#[derive(Debug, Clone, Serialize, Deserialize)]
1012struct WalletIndexes {
1013    /// Next receive address index
1014    receive_index: u32,
1015
1016    /// Next change address index
1017    change_index: u32,
1018
1019    /// Last synced block
1020    last_block: Option<u32>,
1021
1022    /// Last sync time
1023    last_sync: Option<u64>,
1024}
1025
1026/// Fee strategy
1027#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1028pub enum FeeStrategy {
1029    /// Very low fee (might take long to confirm)
1030    VeryLow,
1031
1032    /// Low fee
1033    Low,
1034
1035    /// Medium fee (good balance)
1036    Medium,
1037
1038    /// High fee
1039    High,
1040
1041    /// Very high fee (for urgent transactions)
1042    VeryHigh,
1043
1044    /// Custom fee rate
1045    Custom(FeeRate),
1046}
1047
1048/// Transaction creation parameters
1049#[derive(Debug, Clone, Serialize, Deserialize)]
1050pub struct TransactionParams {
1051    /// List of recipients with amounts
1052    pub recipients: Vec<(String, u64)>,
1053
1054    /// Optional coin selection (use specific UTXOs)
1055    pub utxos: Option<Vec<OutPoint>>,
1056
1057    /// Fee strategy
1058    pub fee_strategy: Option<FeeStrategy>,
1059
1060    /// Lock time
1061    pub lock_time: Option<u32>,
1062
1063    /// Enable RBF (Replace-By-Fee)
1064    pub enable_rbf: bool,
1065
1066    /// Optional change address (if not using the default)
1067    pub change_address: Option<String>,
1068
1069    /// Include metadata in an OP_RETURN output
1070    pub op_return_data: Option<Vec<u8>>,
1071
1072    /// Allow spending unconfirmed UTXOs
1073    pub allow_unconfirmed: bool,
1074}
1075
1076/// Coin selection strategy
1077#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1078pub enum CoinSelectionStrategy {
1079    /// Select largest UTXOs first
1080    LargestFirst,
1081
1082    /// Select smallest UTXOs first
1083    SmallestFirst,
1084
1085    /// Use oldest confirmed first
1086    OldestFirst,
1087
1088    /// Use random selection
1089    Random,
1090
1091    /// Optimize for privacy (avoid change)
1092    PrivacyOptimized,
1093
1094    /// Branch and bound algorithm
1095    BranchAndBound,
1096}
1097
1098// Copyright (C) 2023-2025 Anya Project Contributors
1099// Last Modified: 2025-05-30