1use 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, Taproot, LightningEnabled, MultiChain, }
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, SegWit, NestedSegWit, Taproot, }
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 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 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 fn export_xpriv(&self, password: &str) -> AnyaResult<String>;
115 fn import_xpriv(&self, xpriv: &str, password: &str) -> AnyaResult<()>;
116
117 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 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 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 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 let bitcoin_pubkey = bitcoin::PublicKey::new(public_key);
207 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 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 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 let bitcoin_pubkey = bitcoin::PublicKey::new(public_key);
304 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 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 let bitcoin_pubkey = bitcoin::PublicKey::new(public_key);
348 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 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 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 Ok(())
433 }
434
435 fn broadcast_transaction(&self, tx: &Transaction) -> AnyaResult<String> {
436 Ok(tx.compute_txid().to_string())
438 }
439
440 fn get_transaction(&self, _txid: &str) -> AnyaResult<Option<Transaction>> {
441 Ok(None)
443 }
444
445 fn get_transactions(&self, _limit: usize, _offset: usize) -> AnyaResult<Vec<Transaction>> {
446 Ok(vec![])
448 }
449}
450
451impl BalanceManager for Wallet {
452 fn get_balance(&self) -> AnyaResult<u64> {
453 Ok(0)
455 }
456
457 fn get_unconfirmed_balance(&self) -> AnyaResult<u64> {
458 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 let secret_key = self.derive_key("m/44'/5757'/0'/0/0")?;
506
507 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 let secret_key = self.derive_key("m/44'/137'/0'/0/0")?;
521
522 let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
525 let address_bytes = &public_key.serialize()[1..]; 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 let secret_key = self.derive_key("m/44'/2'/0'/0/0")?;
538
539 let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
542 let address_bytes = &public_key.serialize()[1..]; 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 Err(BitcoinError::Wallet("Not implemented".to_string()).into())
600 }
601
602 fn import_xpriv(&self, _xpriv: &str, _password: &str) -> AnyaResult<()> {
603 Err(BitcoinError::Wallet("Not implemented".to_string()).into())
605 }
606
607 fn backup(&self, _path: &str, _password: &str) -> AnyaResult<()> {
608 Err(BitcoinError::Wallet("Not implemented".to_string()).into())
610 }
611
612 fn restore(&self, _path: &str, _password: &str) -> AnyaResult<()> {
613 Err(BitcoinError::Wallet("Not implemented".to_string()).into())
615 }
616}
617
618fn determine_chain_from_asset_id(asset_id: &str) -> String {
620 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#[derive(Error, Debug)]
634pub enum WalletError {
635 #[error("Bitcoin error: {0}")]
637 BitcoinError(String),
638
639 #[error("Secp256k1 error: {0}")]
641 Secp256k1Error(#[from] secp256k1::Error),
642
643 #[error("BIP39 error: {0}")]
645 Bip39Error(String),
646
647 #[error("Descriptor error: {0}")]
649 DescriptorError(String),
650
651 #[error("Wallet storage error: {0}")]
653 StorageError(String),
654
655 #[error("Wallet configuration error: {0}")]
657 ConfigError(String),
658
659 #[error("Transaction creation error: {0}")]
661 TransactionError(String),
662
663 #[error("PSBT error: {0}")]
665 PsbtError(String),
666
667 #[error("Signing error: {0}")]
669 SigningError(String),
670
671 #[error("Synchronization error: {0}")]
673 SyncError(String),
674
675 #[error("Address generation error: {0}")]
677 AddressError(String),
678
679 #[error("Fee estimation error: {0}")]
681 FeeEstimationError(String),
682
683 #[error("RPC error: {0}")]
685 RpcError(String),
686
687 #[error("Invalid parameters: {0}")]
689 InvalidParameters(String),
690
691 #[error("Insufficient funds: {0}")]
693 InsufficientFunds(String),
694
695 #[error("IO error: {0}")]
697 IoError(#[from] std::io::Error),
698
699 #[error("Serialization error: {0}")]
701 SerializationError(String),
702
703 #[error("UTXO management error: {0}")]
705 UtxoError(String),
706}
707
708#[derive(Debug, Clone, Serialize, Deserialize)]
710pub struct Utxo {
711 pub outpoint: OutPoint,
713
714 pub txout: TxOut,
716
717 pub redeem_script: Option<ScriptBuf>,
719
720 pub witness_script: Option<ScriptBuf>,
722
723 pub confirmations: u32,
725
726 pub spendable: bool,
728
729 pub from_wallet: bool,
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct TransactionInfo {
736 pub txid: Txid,
738
739 pub transaction: Transaction,
741
742 pub block_height: Option<u32>,
744
745 pub confirmations: u32,
747
748 pub fee: Option<u64>,
750
751 pub timestamp: Option<u64>,
753
754 pub sent: u64,
756
757 pub received: u64,
759
760 pub labels: Vec<String>,
762}
763
764#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
766pub enum FeeRate {
767 SatPerKb(u64),
769
770 SatPerVb(u64),
772}
773
774impl FeeRate {
775 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
794pub struct SyncState {
795 pub block_height: u32,
797
798 pub block_hash: String,
800
801 pub last_scan: u64,
803
804 pub progress: f64,
806
807 pub ibd: bool,
809}
810
811#[async_trait]
813pub trait WalletTrait: Send + Sync {
814 async fn init(&self) -> Result<(), WalletError>;
816
817 async fn get_new_address(&self) -> Result<Address, WalletError>;
819
820 async fn get_current_address(&self) -> Result<Address, WalletError>;
822
823 async fn get_change_address(&self) -> Result<Address, WalletError>;
825
826 async fn is_mine(&self, address: &Address) -> Result<bool, WalletError>;
828
829 async fn list_addresses(&self) -> Result<Vec<Address>, WalletError>;
831
832 async fn get_balance(&self) -> Result<u64, WalletError>;
834
835 async fn get_detailed_balance(&self) -> Result<(u64, u64, u64), WalletError>;
837
838 async fn list_utxos(&self) -> Result<Vec<Utxo>, WalletError>;
840
841 async fn get_transactions(&self) -> Result<Vec<TransactionInfo>, WalletError>;
843
844 async fn get_transaction(&self, txid: &Txid) -> Result<Option<TransactionInfo>, WalletError>;
846
847 async fn create_transaction(&self, params: TransactionParams) -> Result<PSBT, WalletError>;
849
850 async fn sign_transaction(&self, psbt: &mut PSBT) -> Result<bool, WalletError>;
852
853 async fn broadcast_transaction(&self, transaction: &Transaction) -> Result<Txid, WalletError>;
855
856 async fn get_fee_rate(&self, strategy: FeeStrategy) -> Result<FeeRate, WalletError>;
858
859 async fn calculate_fee(&self, psbt: &PSBT) -> Result<u64, WalletError>;
861
862 async fn sync(&self) -> Result<SyncState, WalletError>;
864
865 async fn export(&self, path: &Path) -> Result<(), WalletError>;
867
868 async fn import(&self, path: &Path) -> Result<(), WalletError>;
870
871 async fn backup(&self, path: &Path) -> Result<(), WalletError>;
873
874 async fn get_info(&self) -> Result<WalletInfo, WalletError>;
876}
877
878#[derive(Debug, Clone, Serialize, Deserialize)]
880pub struct WalletInfo {
881 pub name: String,
883
884 pub version: String,
886
887 pub format: String,
889
890 pub network: Network,
892
893 pub balance: u64,
895
896 pub unconfirmed_balance: u64,
898
899 pub immature_balance: u64,
901
902 pub keypools: u32,
904
905 pub tx_count: u32,
907
908 pub keypool_oldest: u64,
910
911 pub keypool_size: u32,
913
914 pub private_keys_enabled: bool,
916
917 pub unlocked_until: Option<u64>,
919
920 pub hdseedid: Option<String>,
922
923 pub avoid_reuse: bool,
925
926 pub scanning: bool,
928
929 pub descriptors: bool,
931}
932
933pub struct BitcoinWallet {
935 #[allow(dead_code)]
937 config: WalletConfig,
939
940 #[allow(dead_code)] storage: Arc<Mutex<WalletStorage>>,
943
944 #[allow(dead_code)]
946 secp: Secp256k1<bitcoin::secp256k1::All>,
948}
949
950#[derive(Debug, Serialize, Deserialize)]
952struct WalletStorage {
953 metadata: WalletMetadata,
955
956 utxos: HashMap<OutPoint, Utxo>,
958
959 transactions: HashMap<Txid, TransactionInfo>,
961
962 addresses: HashMap<String, AddressInfo>,
964
965 indexes: WalletIndexes,
967}
968
969#[derive(Debug, Clone, Serialize, Deserialize)]
971struct WalletMetadata {
972 created_at: u64,
974
975 updated_at: u64,
977
978 version: String,
980
981 network: Network,
983
984 master_fingerprint: Option<[u8; 4]>,
986
987 labels: HashMap<String, String>,
989}
990
991#[derive(Debug, Clone, Serialize, Deserialize)]
993pub struct AddressInfo {
994 pub address: String,
996 path: Option<DerivationPath>,
998 script: ScriptBuf,
1000 is_change: bool,
1002 index: u32,
1004 labels: Vec<String>,
1006 last_used: Option<u64>,
1008}
1009
1010#[derive(Debug, Clone, Serialize, Deserialize)]
1012struct WalletIndexes {
1013 receive_index: u32,
1015
1016 change_index: u32,
1018
1019 last_block: Option<u32>,
1021
1022 last_sync: Option<u64>,
1024}
1025
1026#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1028pub enum FeeStrategy {
1029 VeryLow,
1031
1032 Low,
1034
1035 Medium,
1037
1038 High,
1040
1041 VeryHigh,
1043
1044 Custom(FeeRate),
1046}
1047
1048#[derive(Debug, Clone, Serialize, Deserialize)]
1050pub struct TransactionParams {
1051 pub recipients: Vec<(String, u64)>,
1053
1054 pub utxos: Option<Vec<OutPoint>>,
1056
1057 pub fee_strategy: Option<FeeStrategy>,
1059
1060 pub lock_time: Option<u32>,
1062
1063 pub enable_rbf: bool,
1065
1066 pub change_address: Option<String>,
1068
1069 pub op_return_data: Option<Vec<u8>>,
1071
1072 pub allow_unconfirmed: bool,
1074}
1075
1076#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1078pub enum CoinSelectionStrategy {
1079 LargestFirst,
1081
1082 SmallestFirst,
1084
1085 OldestFirst,
1087
1088 Random,
1090
1091 PrivacyOptimized,
1093
1094 BranchAndBound,
1096}
1097
1098