use kaccy_bitcoin::{
BatchOptimizer, BatchStrategy, BatchWithdrawal, BitcoinClient, BitcoinMetrics, BitcoinNetwork,
CoinControl, ConsolidationConfig, InMemoryMetricsBackend, MetricsTimer, SelectionStrategy,
UtxoManager, WithdrawalRequest,
};
use std::sync::Arc;
use tracing::{info, warn};
use uuid::Uuid;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
info!("Batch Withdrawal Processor Example");
info!("==================================");
let bitcoin_rpc_url = "http://localhost:8332";
let bitcoin_rpc_user = "user";
let bitcoin_rpc_pass = "pass";
info!("\n1. Connecting to Bitcoin Core...");
let bitcoin_client = Arc::new(
BitcoinClient::new(
bitcoin_rpc_url,
bitcoin_rpc_user,
bitcoin_rpc_pass,
BitcoinNetwork::Regtest,
)
.expect("Failed to create Bitcoin client"),
);
let metrics_backend = Arc::new(InMemoryMetricsBackend::new());
let metrics = Arc::new(BitcoinMetrics::new(metrics_backend));
info!("\n2. Initializing UTXO manager...");
let consolidation_config = ConsolidationConfig {
min_utxo_count: 100,
max_fee_rate: 10.0,
target_utxo_count: 50,
min_utxo_value_sats: 10_000,
consolidation_address: None,
};
let utxo_manager = UtxoManager::new(bitcoin_client.clone(), consolidation_config);
let coin_control = Arc::new(tokio::sync::RwLock::new(CoinControl::new()));
info!("\n3. Collecting pending withdrawal requests...");
let withdrawal_requests = [
create_mock_withdrawal(
"user1",
"bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
100_000,
),
create_mock_withdrawal(
"user2",
"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
50_000,
),
create_mock_withdrawal(
"user3",
"bc1q9xwjvr8yx8kxwq3qtz5dzt9x9qw8zt6xqy6x5z",
75_000,
),
create_mock_withdrawal(
"user4",
"bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el",
200_000,
),
create_mock_withdrawal(
"user5",
"bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej",
30_000,
),
create_mock_withdrawal(
"user6",
"bc1q2vt8wde46jl6z7aqz07z5h9wkl2v8dxe8xm4jk",
150_000,
),
];
info!(
" Collected {} withdrawal requests",
withdrawal_requests.len()
);
info!(
" Total amount: {} sats",
withdrawal_requests
.iter()
.map(|w| w.amount_sats)
.sum::<u64>()
);
let batch_withdrawals: Vec<BatchWithdrawal> = withdrawal_requests
.iter()
.enumerate()
.map(|(i, w)| BatchWithdrawal {
user_id: format!("user{}", i + 1),
address: w.destination_address.clone(),
amount_sats: w.amount_sats,
priority: if w.amount_sats > 100_000 { 10 } else { 5 },
})
.collect();
info!("\n4. Optimizing batch with different strategies...");
info!("\n Strategy 1: Minimize Transactions");
let batch_optimizer = BatchOptimizer::new(
25, 2, 5.0, );
let optimized_min_tx = batch_optimizer
.optimize(
batch_withdrawals.clone(),
BatchStrategy::MinimizeTransactions,
)
.expect("Failed to optimize batch");
info!(" Transactions: {}", optimized_min_tx.transaction_count);
info!(
" Total fees: {} sats",
optimized_min_tx.estimated_total_fees
);
info!(
" Fee savings: {} sats",
optimized_min_tx.estimated_savings
);
info!("\n Strategy 2: Minimize Fees");
let optimized_min_fees = batch_optimizer
.optimize(batch_withdrawals.clone(), BatchStrategy::MinimizeFees)
.expect("Failed to optimize batch");
info!(" Transactions: {}", optimized_min_fees.transaction_count);
info!(
" Total fees: {} sats",
optimized_min_fees.estimated_total_fees
);
info!(
" Fee savings: {} sats vs individual txs",
optimized_min_fees.estimated_savings
);
info!("\n Strategy 3: Balanced");
let optimized_balanced = batch_optimizer
.optimize(batch_withdrawals.clone(), BatchStrategy::Balanced)
.expect("Failed to optimize batch");
info!(" Transactions: {}", optimized_balanced.transaction_count);
info!(
" Total fees: {} sats",
optimized_balanced.estimated_total_fees
);
info!("\n5. Checking available UTXOs...");
match utxo_manager.list_utxos() {
Ok(utxos) => {
info!(" Available UTXOs: {}", utxos.len());
let total_available: u64 = utxos.iter().map(|u| u.amount_sats).sum();
info!(" Total available: {} sats", total_available);
let coin_control_guard = coin_control.read().await;
let filtered_utxos = coin_control_guard.filter_utxos(utxos.clone());
info!(" After coin control: {} UTXOs", filtered_utxos.len());
if !filtered_utxos.is_empty() {
info!("\n6. Selecting UTXOs for batch withdrawal...");
let timer = MetricsTimer::start();
let total_needed: u64 =
batch_withdrawals.iter().map(|w| w.amount_sats).sum::<u64>() + 50_000;
match utxo_manager.select_utxos(total_needed, 5.0, SelectionStrategy::LargestFirst)
{
Ok(selection) => {
let duration = timer.stop();
info!(" Selected {} UTXOs", selection.selected.len());
info!(" Total amount: {} sats", selection.total_input_sats);
info!(" Estimated fee: {} sats", selection.estimated_fee_sats);
info!(" Change: {} sats", selection.change_sats);
metrics
.record_utxo_selection(
"largest_first",
selection.selected.len(),
selection.total_input_sats,
duration,
)
.await;
}
Err(e) => {
warn!(" Failed to select UTXOs: {}", e);
}
}
}
}
Err(e) => {
warn!(" Failed to list UTXOs: {}", e);
warn!(" Note: This example requires a running Bitcoin Core node");
}
}
info!("\n7. Creating batch withdrawal transactions...");
info!(" (In production, these would be signed and broadcast)");
for (i, batch) in optimized_balanced.batches.iter().enumerate() {
info!("\n Batch {}:", i + 1);
info!(" Withdrawals: {}", batch.len());
info!(
" Total amount: {} sats",
batch.iter().map(|w| w.amount_sats).sum::<u64>()
);
info!(" Recipients:");
for withdrawal in batch {
info!(
" - {} -> {} sats to {}",
withdrawal.user_id,
withdrawal.amount_sats,
&withdrawal.address[..20]
);
}
}
info!("\n8. Metrics Summary:");
let stats = metrics.get_stats().await;
info!(" Total UTXO selections: {}", stats.total_utxo_selections);
info!("\nBatch Withdrawal Processor Example Complete!");
info!("\nKey Benefits of Batch Processing:");
info!(
" 1. Significant fee savings ({:.2}% saved)",
(optimized_balanced.estimated_savings as f64
/ (optimized_balanced.estimated_total_fees + optimized_balanced.estimated_savings)
as f64)
* 100.0
);
info!(
" 2. Fewer transactions on-chain ({} vs {} individual)",
optimized_balanced.transaction_count,
batch_withdrawals.len()
);
info!(" 3. Better UTXO management");
info!(" 4. Improved privacy (multiple payments in one tx)");
info!("\nProduction Considerations:");
info!(" 1. Implement proper signing workflow (hardware wallet/HSM)");
info!(" 2. Add retry logic for failed broadcasts");
info!(" 3. Monitor transaction confirmations");
info!(" 4. Handle edge cases (RBF, fee bumping)");
info!(" 5. Implement withdrawal queue management");
info!(" 6. Add proper error handling and rollback");
info!(" 7. Set up monitoring and alerting");
Ok(())
}
fn create_mock_withdrawal(_user_id: &str, address: &str, amount_sats: u64) -> WithdrawalRequest {
WithdrawalRequest {
user_id: Uuid::new_v4(), destination_address: address.to_string(),
amount_sats,
fee_rate: Some(5), }
}