use crate::bitcoin::error::BitcoinError;
use crate::bitcoin::interface::BitcoinInterface;
use crate::{AnyaError, AnyaResult};
use async_trait::async_trait;
use bitcoin::absolute::LockTime;
use bitcoin::bip32::DerivationPath;
use bitcoin::hashes::Hash;
use bitcoin::psbt::Psbt as PSBT;
use bitcoin::secp256k1::{Secp256k1, SecretKey};
use bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid};
use bitcoin::{Amount, ScriptBuf};
use log::error;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use thiserror::Error;
pub mod bip32;
pub mod transactions;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WalletType {
Standard, Taproot, LightningEnabled, MultiChain, }
pub struct WalletConfig {
pub wallet_type: WalletType,
pub network: Network,
pub name: String,
pub seed_phrase: Option<String>,
pub password: Option<String>,
pub receive_descriptor: String,
pub change_descriptor: String,
pub xpub: Option<String>,
pub data_dir: PathBuf,
pub use_rpc: bool,
pub coin_selection: CoinSelectionStrategy,
pub gap_limit: u32,
pub min_confirmations: u32,
pub fee_strategy: FeeStrategy,
}
pub trait KeyManager {
fn derive_key(&self, path: &str) -> AnyaResult<SecretKey>;
fn get_public_key(&self, path: &str) -> AnyaResult<bitcoin::secp256k1::PublicKey>;
fn sign_message(&self, message: &[u8], path: &str) -> AnyaResult<Vec<u8>>;
fn verify_message(&self, message: &[u8], _signature: &[u8], path: &str) -> AnyaResult<bool>;
}
pub trait AddressManager {
fn get_new_address(&self, address_type: AddressType) -> AnyaResult<Address>;
fn get_address(&self, index: u32, address_type: AddressType) -> AnyaResult<Address>;
fn is_address_mine(&self, address: &str) -> AnyaResult<bool>;
fn get_all_addresses(&self) -> AnyaResult<Vec<Address>>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AddressType {
Legacy, SegWit, NestedSegWit, Taproot, }
pub trait TransactionManager {
fn create_transaction(
&self,
outputs: Vec<(String, u64)>,
_fee_rate: f64,
_options: transactions::TxOptions,
) -> AnyaResult<Transaction>;
fn sign_transaction(&self, tx: &mut Transaction) -> AnyaResult<()>;
fn broadcast_transaction(&self, tx: &Transaction) -> AnyaResult<String>;
fn get_transaction(&self, txid: &str) -> AnyaResult<Option<Transaction>>;
fn get_transactions(&self, limit: usize, offset: usize) -> AnyaResult<Vec<Transaction>>;
}
pub trait BalanceManager {
fn get_balance(&self) -> AnyaResult<u64>;
fn get_unconfirmed_balance(&self) -> AnyaResult<u64>;
fn get_asset_balance(&self, asset_id: &str) -> AnyaResult<u64>;
fn get_all_asset_balances(&self) -> AnyaResult<HashMap<String, u64>>;
}
pub trait UnifiedWallet: KeyManager + AddressManager + TransactionManager + BalanceManager {
fn name(&self) -> &str;
fn wallet_type(&self) -> WalletType;
fn network(&self) -> Network;
fn get_stacks_address(&self) -> AnyaResult<String>;
fn get_rsk_address(&self) -> AnyaResult<String>;
fn get_liquid_address(&self) -> AnyaResult<String>;
fn add_asset(&self, asset_id: &str, name: &str, asset_type: &str) -> AnyaResult<()>;
fn remove_asset(&self, asset_id: &str) -> AnyaResult<()>;
fn get_assets(&self) -> AnyaResult<Vec<Asset>>;
fn export_xpriv(&self, password: &str) -> AnyaResult<String>;
fn import_xpriv(&self, xpriv: &str, password: &str) -> AnyaResult<()>;
fn backup(&self, path: &str, password: &str) -> AnyaResult<()>;
fn restore(&self, path: &str, password: &str) -> AnyaResult<()>;
}
#[derive(Clone)]
pub struct Asset {
pub id: String,
pub name: String,
pub asset_type: String,
pub chain: String,
pub balance: u64,
pub metadata: HashMap<String, String>,
}
#[allow(dead_code)]
pub struct Wallet {
config: WalletConfig,
seed: Mutex<Option<[u8; 64]>>,
secp: Secp256k1<bitcoin::secp256k1::All>,
addresses: Mutex<HashMap<AddressType, Vec<Address>>>,
assets: Mutex<HashMap<String, Asset>>,
transactions: Mutex<Vec<Transaction>>,
bitcoin_client: Option<Arc<dyn BitcoinInterface>>,
}
impl Wallet {
pub fn new(config: WalletConfig, bitcoin_client: Option<Arc<dyn BitcoinInterface>>) -> Self {
Self {
config,
seed: Mutex::new(None),
secp: Secp256k1::new(),
addresses: Mutex::new(HashMap::new()),
assets: Mutex::new(HashMap::new()),
transactions: Mutex::new(Vec::new()),
bitcoin_client,
}
}
pub fn initialize(&self, seed_phrase: Option<&str>, password: Option<&str>) -> AnyaResult<()> {
let seed = if let Some(phrase) = seed_phrase {
bip32::seed_from_mnemonic(phrase, password.unwrap_or(""))?
} else {
bip32::generate_seed(password.unwrap_or(""))?
};
let mut seed_guard = self
.seed
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
*seed_guard = Some(seed);
self.init_addresses()?;
Ok(())
}
fn init_addresses(&self) -> AnyaResult<()> {
let mut addresses = self
.addresses
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
for address_type in [
AddressType::Legacy,
AddressType::SegWit,
AddressType::NestedSegWit,
AddressType::Taproot,
]
.iter()
{
let mut type_addresses = Vec::new();
for i in 0..20 {
let path = match address_type {
AddressType::Legacy => format!("m/44'/0'/0'/0/{i}"),
AddressType::SegWit => format!("m/84'/0'/0'/0/{i}"),
AddressType::NestedSegWit => format!("m/49'/0'/0'/0/{i}"),
AddressType::Taproot => format!("m/86'/0'/0'/0/{i}"),
};
let secret_key = self.derive_key(&path)?;
let public_key =
bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
let bitcoin_pubkey = bitcoin::PublicKey::new(public_key);
let compressed_pubkey = bitcoin::key::CompressedPublicKey::from_slice(
&bitcoin_pubkey.inner.serialize(),
)?;
let address = match address_type {
AddressType::Legacy => Address::p2pkh(bitcoin_pubkey, self.config.network),
AddressType::SegWit => Address::p2wpkh(&compressed_pubkey, self.config.network),
AddressType::NestedSegWit => {
Address::p2shwpkh(&compressed_pubkey, self.config.network)
}
AddressType::Taproot => {
let xonly = bitcoin::secp256k1::XOnlyPublicKey::from(public_key);
Address::p2tr(&self.secp, xonly, None, self.config.network)
}
};
type_addresses.push(address);
}
addresses.insert(*address_type, type_addresses);
}
Ok(())
}
}
impl KeyManager for Wallet {
fn derive_key(&self, path: &str) -> AnyaResult<SecretKey> {
let seed_guard = self
.seed
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
let seed = seed_guard
.as_ref()
.ok_or_else(|| BitcoinError::Wallet("Wallet not initialized".to_string()))?;
bip32::derive_key_from_seed(seed, path).map_err(|e| AnyaError::Bitcoin(e.to_string()))
}
fn get_public_key(&self, path: &str) -> AnyaResult<bitcoin::secp256k1::PublicKey> {
let private_key = self.derive_key(path)?;
let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &private_key);
Ok(public_key)
}
fn sign_message(&self, message: &[u8], path: &str) -> AnyaResult<Vec<u8>> {
let private_key = self.derive_key(path)?;
let hash = bitcoin::hashes::sha256::Hash::hash(message);
let message_hash = bitcoin::secp256k1::Message::from_digest(hash.to_byte_array());
let signature = self.secp.sign_ecdsa(&message_hash, &private_key);
Ok(signature.serialize_der().to_vec())
}
fn verify_message(&self, message: &[u8], signature: &[u8], path: &str) -> AnyaResult<bool> {
let public_key = self.get_public_key(path)?;
let hash = bitcoin::hashes::sha256::Hash::hash(message);
let message_hash = bitcoin::secp256k1::Message::from_digest(hash.to_byte_array());
let signature = bitcoin::secp256k1::ecdsa::Signature::from_der(signature)
.map_err(|e| BitcoinError::Wallet(format!("Invalid signature: {e}")))?;
Ok(self
.secp
.verify_ecdsa(&message_hash, &signature, &public_key)
.is_ok())
}
}
impl AddressManager for Wallet {
fn get_new_address(&self, address_type: AddressType) -> AnyaResult<Address> {
let mut addresses = self
.addresses
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
let type_addresses = addresses.entry(address_type).or_insert_with(Vec::new);
let index = type_addresses.len() as u32;
let path = match address_type {
AddressType::Legacy => format!("m/44'/0'/0'/0/{index}"),
AddressType::SegWit => format!("m/84'/0'/0'/0/{index}"),
AddressType::NestedSegWit => format!("m/49'/0'/0'/0/{index}"),
AddressType::Taproot => format!("m/86'/0'/0'/0/{index}"),
};
let secret_key = self.derive_key(&path)?;
let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
let bitcoin_pubkey = bitcoin::PublicKey::new(public_key);
let compressed_pubkey =
bitcoin::key::CompressedPublicKey::from_slice(&bitcoin_pubkey.inner.serialize())?;
let address = match address_type {
AddressType::Legacy => Address::p2pkh(bitcoin_pubkey, self.config.network),
AddressType::SegWit => Address::p2wpkh(&compressed_pubkey, self.config.network),
AddressType::NestedSegWit => Address::p2shwpkh(&compressed_pubkey, self.config.network),
AddressType::Taproot => {
let xonly = bitcoin::secp256k1::XOnlyPublicKey::from(public_key);
Address::p2tr(&self.secp, xonly, None, self.config.network)
}
};
type_addresses.push(address.clone());
Ok(address)
}
fn get_address(&self, index: u32, address_type: AddressType) -> AnyaResult<Address> {
let addresses = self
.addresses
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
if let Some(type_addresses) = addresses.get(&address_type) {
if let Some(address) = type_addresses.get(index as usize) {
return Ok(address.clone());
}
}
let path = match address_type {
AddressType::Legacy => format!("m/44'/0'/0'/0/{index}"),
AddressType::SegWit => format!("m/84'/0'/0'/0/{index}"),
AddressType::NestedSegWit => format!("m/49'/0'/0'/0/{index}"),
AddressType::Taproot => format!("m/86'/0'/0'/0/{index}"),
};
let secret_key = self.derive_key(&path)?;
let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
let bitcoin_pubkey = bitcoin::PublicKey::new(public_key);
let compressed_pubkey =
bitcoin::key::CompressedPublicKey::from_slice(&bitcoin_pubkey.inner.serialize())?;
let address = match address_type {
AddressType::Legacy => Address::p2pkh(bitcoin_pubkey, self.config.network),
AddressType::SegWit => Address::p2wpkh(&compressed_pubkey, self.config.network),
AddressType::NestedSegWit => Address::p2shwpkh(&compressed_pubkey, self.config.network),
AddressType::Taproot => {
let xonly = bitcoin::secp256k1::XOnlyPublicKey::from(public_key);
Address::p2tr(&self.secp, xonly, None, self.config.network)
}
};
Ok(address)
}
fn is_address_mine(&self, address: &str) -> AnyaResult<bool> {
let addresses = self
.addresses
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
for type_addresses in addresses.values() {
for addr in type_addresses {
if addr.to_string() == address {
return Ok(true);
}
}
}
Ok(false)
}
fn get_all_addresses(&self) -> AnyaResult<Vec<Address>> {
let addresses = self
.addresses
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
let mut result = Vec::new();
for type_addresses in addresses.values() {
result.extend(type_addresses.clone());
}
Ok(result)
}
}
impl TransactionManager for Wallet {
fn create_transaction(
&self,
outputs: Vec<(String, u64)>,
_fee_rate: f64,
_options: transactions::TxOptions,
) -> AnyaResult<Transaction> {
let mut tx_outs = Vec::new();
for (addr, amount) in outputs {
let script_pubkey = Address::from_str(&addr)
.map_err(|e| BitcoinError::Wallet(format!("Invalid address: {e}")))?
.require_network(self.config.network)
.map_err(|e| BitcoinError::Wallet(format!("Network mismatch: {e}")))?
.script_pubkey();
tx_outs.push(TxOut {
value: Amount::from_sat(amount),
script_pubkey,
});
}
Ok(Transaction {
version: bitcoin::transaction::Version(2),
lock_time: LockTime::ZERO,
input: vec![],
output: tx_outs,
})
}
fn sign_transaction(&self, _tx: &mut Transaction) -> AnyaResult<()> {
Ok(())
}
fn broadcast_transaction(&self, tx: &Transaction) -> AnyaResult<String> {
Ok(tx.compute_txid().to_string())
}
fn get_transaction(&self, _txid: &str) -> AnyaResult<Option<Transaction>> {
Ok(None)
}
fn get_transactions(&self, _limit: usize, _offset: usize) -> AnyaResult<Vec<Transaction>> {
Ok(vec![])
}
}
impl BalanceManager for Wallet {
fn get_balance(&self) -> AnyaResult<u64> {
Ok(0)
}
fn get_unconfirmed_balance(&self) -> AnyaResult<u64> {
Ok(0)
}
fn get_asset_balance(&self, asset_id: &str) -> AnyaResult<u64> {
let assets = self
.assets
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
if let Some(asset) = assets.get(asset_id) {
Ok(asset.balance)
} else {
Err(BitcoinError::Wallet(format!("Asset not found: {asset_id}")).into())
}
}
fn get_all_asset_balances(&self) -> AnyaResult<HashMap<String, u64>> {
let assets = self
.assets
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
let mut balances = HashMap::new();
for (id, asset) in assets.iter() {
balances.insert(id.clone(), asset.balance);
}
Ok(balances)
}
}
impl UnifiedWallet for Wallet {
fn name(&self) -> &str {
&self.config.name
}
fn wallet_type(&self) -> WalletType {
self.config.wallet_type.clone()
}
fn network(&self) -> Network {
self.config.network
}
fn get_stacks_address(&self) -> AnyaResult<String> {
let secret_key = self.derive_key("m/44'/5757'/0'/0/0")?;
let address_hash = format!(
"{:x}",
secret_key.secret_bytes()[0..20]
.iter()
.fold(0u64, |acc, &b| acc.wrapping_mul(256).wrapping_add(b as u64))
);
Ok(format!("ST{}", &address_hash[..32].to_uppercase()))
}
fn get_rsk_address(&self) -> AnyaResult<String> {
let secret_key = self.derive_key("m/44'/137'/0'/0/0")?;
let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
let address_bytes = &public_key.serialize()[1..]; let address_hash = format!(
"{:02x}",
address_bytes[0..20]
.iter()
.fold(0u64, |acc, &b| acc.wrapping_mul(256).wrapping_add(b as u64))
);
Ok(format!("0x{}", &address_hash[..40]))
}
fn get_liquid_address(&self) -> AnyaResult<String> {
let secret_key = self.derive_key("m/44'/2'/0'/0/0")?;
let public_key = bitcoin::secp256k1::PublicKey::from_secret_key(&self.secp, &secret_key);
let address_bytes = &public_key.serialize()[1..]; let address_hash = format!(
"{:02x}",
address_bytes[0..25].iter().fold(0u128, |acc, &b| acc
.wrapping_mul(256)
.wrapping_add(b as u128))
);
Ok(format!("VT{}", &address_hash[..50]))
}
fn add_asset(&self, asset_id: &str, name: &str, asset_type: &str) -> AnyaResult<()> {
let mut assets = self
.assets
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
if assets.contains_key(asset_id) {
return Err(BitcoinError::Wallet(format!("Asset already exists: {asset_id}")).into());
}
let asset = Asset {
id: asset_id.to_string(),
name: name.to_string(),
asset_type: asset_type.to_string(),
chain: determine_chain_from_asset_id(asset_id),
balance: 0,
metadata: HashMap::new(),
};
assets.insert(asset_id.to_string(), asset);
Ok(())
}
fn remove_asset(&self, asset_id: &str) -> AnyaResult<()> {
let mut assets = self
.assets
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
if assets.remove(asset_id).is_none() {
return Err(BitcoinError::Wallet(format!("Asset not found: {asset_id}")).into());
}
Ok(())
}
fn get_assets(&self) -> AnyaResult<Vec<Asset>> {
let assets = self
.assets
.lock()
.map_err(|e| format!("Mutex lock error: {e}"))?;
Ok(assets.values().cloned().collect())
}
fn export_xpriv(&self, _password: &str) -> AnyaResult<String> {
Err(BitcoinError::Wallet("Not implemented".to_string()).into())
}
fn import_xpriv(&self, _xpriv: &str, _password: &str) -> AnyaResult<()> {
Err(BitcoinError::Wallet("Not implemented".to_string()).into())
}
fn backup(&self, _path: &str, _password: &str) -> AnyaResult<()> {
Err(BitcoinError::Wallet("Not implemented".to_string()).into())
}
fn restore(&self, _path: &str, _password: &str) -> AnyaResult<()> {
Err(BitcoinError::Wallet("Not implemented".to_string()).into())
}
}
fn determine_chain_from_asset_id(asset_id: &str) -> String {
if asset_id.starts_with("btc-") {
"Bitcoin".to_string()
} else if asset_id.starts_with("lq-") {
"Liquid".to_string()
} else if asset_id.starts_with("rsk-") {
"RSK".to_string()
} else {
"Unknown".to_string()
}
}
#[derive(Error, Debug)]
pub enum WalletError {
#[error("Bitcoin error: {0}")]
BitcoinError(String),
#[error("Secp256k1 error: {0}")]
Secp256k1Error(#[from] secp256k1::Error),
#[error("BIP39 error: {0}")]
Bip39Error(String),
#[error("Descriptor error: {0}")]
DescriptorError(String),
#[error("Wallet storage error: {0}")]
StorageError(String),
#[error("Wallet configuration error: {0}")]
ConfigError(String),
#[error("Transaction creation error: {0}")]
TransactionError(String),
#[error("PSBT error: {0}")]
PsbtError(String),
#[error("Signing error: {0}")]
SigningError(String),
#[error("Synchronization error: {0}")]
SyncError(String),
#[error("Address generation error: {0}")]
AddressError(String),
#[error("Fee estimation error: {0}")]
FeeEstimationError(String),
#[error("RPC error: {0}")]
RpcError(String),
#[error("Invalid parameters: {0}")]
InvalidParameters(String),
#[error("Insufficient funds: {0}")]
InsufficientFunds(String),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("UTXO management error: {0}")]
UtxoError(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Utxo {
pub outpoint: OutPoint,
pub txout: TxOut,
pub redeem_script: Option<ScriptBuf>,
pub witness_script: Option<ScriptBuf>,
pub confirmations: u32,
pub spendable: bool,
pub from_wallet: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionInfo {
pub txid: Txid,
pub transaction: Transaction,
pub block_height: Option<u32>,
pub confirmations: u32,
pub fee: Option<u64>,
pub timestamp: Option<u64>,
pub sent: u64,
pub received: u64,
pub labels: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FeeRate {
SatPerKb(u64),
SatPerVb(u64),
}
impl FeeRate {
pub fn to_sat_per_vb(&self) -> u64 {
match self {
FeeRate::SatPerKb(fee) => (fee + 999) / 1000,
FeeRate::SatPerVb(fee) => *fee,
}
}
pub fn to_sat_per_kb(&self) -> u64 {
match self {
FeeRate::SatPerKb(fee) => *fee,
FeeRate::SatPerVb(fee) => fee * 1000,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncState {
pub block_height: u32,
pub block_hash: String,
pub last_scan: u64,
pub progress: f64,
pub ibd: bool,
}
#[async_trait]
pub trait WalletTrait: Send + Sync {
async fn init(&self) -> Result<(), WalletError>;
async fn get_new_address(&self) -> Result<Address, WalletError>;
async fn get_current_address(&self) -> Result<Address, WalletError>;
async fn get_change_address(&self) -> Result<Address, WalletError>;
async fn is_mine(&self, address: &Address) -> Result<bool, WalletError>;
async fn list_addresses(&self) -> Result<Vec<Address>, WalletError>;
async fn get_balance(&self) -> Result<u64, WalletError>;
async fn get_detailed_balance(&self) -> Result<(u64, u64, u64), WalletError>;
async fn list_utxos(&self) -> Result<Vec<Utxo>, WalletError>;
async fn get_transactions(&self) -> Result<Vec<TransactionInfo>, WalletError>;
async fn get_transaction(&self, txid: &Txid) -> Result<Option<TransactionInfo>, WalletError>;
async fn create_transaction(&self, params: TransactionParams) -> Result<PSBT, WalletError>;
async fn sign_transaction(&self, psbt: &mut PSBT) -> Result<bool, WalletError>;
async fn broadcast_transaction(&self, transaction: &Transaction) -> Result<Txid, WalletError>;
async fn get_fee_rate(&self, strategy: FeeStrategy) -> Result<FeeRate, WalletError>;
async fn calculate_fee(&self, psbt: &PSBT) -> Result<u64, WalletError>;
async fn sync(&self) -> Result<SyncState, WalletError>;
async fn export(&self, path: &Path) -> Result<(), WalletError>;
async fn import(&self, path: &Path) -> Result<(), WalletError>;
async fn backup(&self, path: &Path) -> Result<(), WalletError>;
async fn get_info(&self) -> Result<WalletInfo, WalletError>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WalletInfo {
pub name: String,
pub version: String,
pub format: String,
pub network: Network,
pub balance: u64,
pub unconfirmed_balance: u64,
pub immature_balance: u64,
pub keypools: u32,
pub tx_count: u32,
pub keypool_oldest: u64,
pub keypool_size: u32,
pub private_keys_enabled: bool,
pub unlocked_until: Option<u64>,
pub hdseedid: Option<String>,
pub avoid_reuse: bool,
pub scanning: bool,
pub descriptors: bool,
}
pub struct BitcoinWallet {
#[allow(dead_code)]
config: WalletConfig,
#[allow(dead_code)] storage: Arc<Mutex<WalletStorage>>,
#[allow(dead_code)]
secp: Secp256k1<bitcoin::secp256k1::All>,
}
#[derive(Debug, Serialize, Deserialize)]
struct WalletStorage {
metadata: WalletMetadata,
utxos: HashMap<OutPoint, Utxo>,
transactions: HashMap<Txid, TransactionInfo>,
addresses: HashMap<String, AddressInfo>,
indexes: WalletIndexes,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct WalletMetadata {
created_at: u64,
updated_at: u64,
version: String,
network: Network,
master_fingerprint: Option<[u8; 4]>,
labels: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddressInfo {
pub address: String,
path: Option<DerivationPath>,
script: ScriptBuf,
is_change: bool,
index: u32,
labels: Vec<String>,
last_used: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct WalletIndexes {
receive_index: u32,
change_index: u32,
last_block: Option<u32>,
last_sync: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FeeStrategy {
VeryLow,
Low,
Medium,
High,
VeryHigh,
Custom(FeeRate),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionParams {
pub recipients: Vec<(String, u64)>,
pub utxos: Option<Vec<OutPoint>>,
pub fee_strategy: Option<FeeStrategy>,
pub lock_time: Option<u32>,
pub enable_rbf: bool,
pub change_address: Option<String>,
pub op_return_data: Option<Vec<u8>>,
pub allow_unconfirmed: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CoinSelectionStrategy {
LargestFirst,
SmallestFirst,
OldestFirst,
Random,
PrivacyOptimized,
BranchAndBound,
}