use bitcoin::{Amount, Network};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use crate::error::{BitcoinError, Result};
use crate::timelock::{HtlcContract, HtlcManager, TimeLockType};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SwapRole {
Initiator,
Participant,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SwapStatus {
Initiated,
Locked,
Completed,
Refunded,
Cancelled,
}
#[derive(Debug, Clone)]
pub struct AtomicSwapConfig {
pub network: Network,
pub initiator_timelock_blocks: u32,
pub participant_timelock_blocks: u32,
}
impl Default for AtomicSwapConfig {
fn default() -> Self {
Self {
network: Network::Bitcoin,
initiator_timelock_blocks: 288, participant_timelock_blocks: 144, }
}
}
#[derive(Debug, Clone)]
pub struct AtomicSwap {
pub swap_id: String,
pub role: SwapRole,
pub payment_hash: [u8; 32],
pub preimage: Option<Vec<u8>>,
pub initiator_pubkey: Vec<u8>,
pub participant_pubkey: Vec<u8>,
pub amount: Amount,
pub htlc: HtlcContract,
pub status: SwapStatus,
pub created_at: DateTime<Utc>,
pub funding_txid: Option<String>,
pub claim_txid: Option<String>,
}
pub struct AtomicSwapManager {
config: AtomicSwapConfig,
htlc_manager: HtlcManager,
active_swaps: Arc<RwLock<HashMap<String, AtomicSwap>>>,
}
impl AtomicSwapManager {
pub fn new(config: AtomicSwapConfig) -> Self {
let htlc_manager = HtlcManager::new(config.network);
Self {
config,
htlc_manager,
active_swaps: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn initiate_swap(
&self,
swap_id: String,
amount: Amount,
initiator_pubkey: Vec<u8>,
participant_pubkey: Vec<u8>,
) -> Result<AtomicSwap> {
let preimage = self.generate_preimage();
let payment_hash = HtlcManager::generate_payment_hash(&preimage);
let timelock = TimeLockType::RelativeBlocks(self.config.initiator_timelock_blocks as u16);
let htlc = self.htlc_manager.create_htlc(
payment_hash,
initiator_pubkey.clone(),
participant_pubkey.clone(),
timelock,
)?;
let swap = AtomicSwap {
swap_id: swap_id.clone(),
role: SwapRole::Initiator,
payment_hash,
preimage: Some(preimage),
initiator_pubkey,
participant_pubkey,
amount,
htlc,
status: SwapStatus::Initiated,
created_at: Utc::now(),
funding_txid: None,
claim_txid: None,
};
let mut swaps = self.active_swaps.write().unwrap();
swaps.insert(swap_id, swap.clone());
Ok(swap)
}
pub fn participate_swap(
&self,
swap_id: String,
payment_hash: [u8; 32],
amount: Amount,
initiator_pubkey: Vec<u8>,
participant_pubkey: Vec<u8>,
) -> Result<AtomicSwap> {
let timelock = TimeLockType::RelativeBlocks(self.config.participant_timelock_blocks as u16);
let htlc = self.htlc_manager.create_htlc(
payment_hash,
participant_pubkey.clone(),
initiator_pubkey.clone(),
timelock,
)?;
let swap = AtomicSwap {
swap_id: swap_id.clone(),
role: SwapRole::Participant,
payment_hash,
preimage: None, initiator_pubkey,
participant_pubkey,
amount,
htlc,
status: SwapStatus::Initiated,
created_at: Utc::now(),
funding_txid: None,
claim_txid: None,
};
let mut swaps = self.active_swaps.write().unwrap();
swaps.insert(swap_id, swap.clone());
Ok(swap)
}
pub fn mark_funded(&self, swap_id: &str, funding_txid: String) -> Result<()> {
let mut swaps = self.active_swaps.write().unwrap();
let swap = swaps
.get_mut(swap_id)
.ok_or_else(|| BitcoinError::Validation(format!("Swap {} not found", swap_id)))?;
swap.funding_txid = Some(funding_txid);
swap.status = SwapStatus::Locked;
swap.htlc.mark_active();
Ok(())
}
pub fn claim_swap(&self, swap_id: &str, preimage: Vec<u8>) -> Result<Vec<u8>> {
let mut swaps = self.active_swaps.write().unwrap();
let swap = swaps
.get_mut(swap_id)
.ok_or_else(|| BitcoinError::Validation(format!("Swap {} not found", swap_id)))?;
if !HtlcManager::verify_preimage(&preimage, &swap.payment_hash) {
return Err(BitcoinError::Validation("Invalid preimage".to_string()));
}
swap.preimage = Some(preimage.clone());
swap.status = SwapStatus::Completed;
swap.htlc.mark_claimed();
Ok(preimage)
}
pub fn refund_swap(&self, swap_id: &str) -> Result<()> {
let mut swaps = self.active_swaps.write().unwrap();
let swap = swaps
.get_mut(swap_id)
.ok_or_else(|| BitcoinError::Validation(format!("Swap {} not found", swap_id)))?;
swap.status = SwapStatus::Refunded;
swap.htlc.mark_refunded();
Ok(())
}
pub fn get_swap(&self, swap_id: &str) -> Option<AtomicSwap> {
let swaps = self.active_swaps.read().unwrap();
swaps.get(swap_id).cloned()
}
pub fn list_swaps(&self) -> Vec<AtomicSwap> {
let swaps = self.active_swaps.read().unwrap();
swaps.values().cloned().collect()
}
fn generate_preimage(&self) -> Vec<u8> {
use bitcoin::secp256k1::rand::RngCore;
use bitcoin::secp256k1::rand::rngs::OsRng;
let mut preimage = vec![0u8; 32];
OsRng.fill_bytes(&mut preimage);
preimage
}
pub fn cancel_swap(&self, swap_id: &str) -> Result<()> {
let mut swaps = self.active_swaps.write().unwrap();
let swap = swaps
.get_mut(swap_id)
.ok_or_else(|| BitcoinError::Validation(format!("Swap {} not found", swap_id)))?;
if swap.status != SwapStatus::Initiated {
return Err(BitcoinError::Validation(
"Can only cancel initiated swaps".to_string(),
));
}
swap.status = SwapStatus::Cancelled;
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SwapStep {
InitiatorCreate,
InitiatorLock,
ParticipantCreate,
ParticipantLock,
InitiatorClaim,
ParticipantClaim,
Refund,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_atomic_swap_config_defaults() {
let config = AtomicSwapConfig::default();
assert_eq!(config.network, Network::Bitcoin);
assert_eq!(config.initiator_timelock_blocks, 288);
assert_eq!(config.participant_timelock_blocks, 144);
assert!(config.initiator_timelock_blocks > config.participant_timelock_blocks);
}
#[test]
fn test_initiate_swap() {
let config = AtomicSwapConfig {
network: Network::Testnet,
..Default::default()
};
let manager = AtomicSwapManager::new(config);
let swap = manager
.initiate_swap(
"swap1".to_string(),
Amount::from_sat(100000),
vec![0x02; 33],
vec![0x03; 33],
)
.unwrap();
assert_eq!(swap.swap_id, "swap1");
assert_eq!(swap.role, SwapRole::Initiator);
assert_eq!(swap.status, SwapStatus::Initiated);
assert!(swap.preimage.is_some());
}
#[test]
fn test_participate_swap() {
let config = AtomicSwapConfig {
network: Network::Testnet,
..Default::default()
};
let manager = AtomicSwapManager::new(config);
let payment_hash = HtlcManager::generate_payment_hash(b"secret");
let swap = manager
.participate_swap(
"swap1".to_string(),
payment_hash,
Amount::from_sat(100000),
vec![0x02; 33],
vec![0x03; 33],
)
.unwrap();
assert_eq!(swap.swap_id, "swap1");
assert_eq!(swap.role, SwapRole::Participant);
assert_eq!(swap.status, SwapStatus::Initiated);
assert!(swap.preimage.is_none());
}
#[test]
fn test_swap_lifecycle() {
let config = AtomicSwapConfig {
network: Network::Testnet,
..Default::default()
};
let manager = AtomicSwapManager::new(config);
let swap = manager
.initiate_swap(
"swap1".to_string(),
Amount::from_sat(100000),
vec![0x02; 33],
vec![0x03; 33],
)
.unwrap();
manager.mark_funded("swap1", "txid123".to_string()).unwrap();
let funded_swap = manager.get_swap("swap1").unwrap();
assert_eq!(funded_swap.status, SwapStatus::Locked);
let preimage = swap.preimage.clone().unwrap();
manager.claim_swap("swap1", preimage).unwrap();
let completed_swap = manager.get_swap("swap1").unwrap();
assert_eq!(completed_swap.status, SwapStatus::Completed);
}
#[test]
fn test_claim_with_invalid_preimage() {
let config = AtomicSwapConfig {
network: Network::Testnet,
..Default::default()
};
let manager = AtomicSwapManager::new(config);
manager
.initiate_swap(
"swap1".to_string(),
Amount::from_sat(100000),
vec![0x02; 33],
vec![0x03; 33],
)
.unwrap();
manager.mark_funded("swap1", "txid123".to_string()).unwrap();
let wrong_preimage = b"wrong_preimage".to_vec();
let result = manager.claim_swap("swap1", wrong_preimage);
assert!(result.is_err());
}
#[test]
fn test_refund_swap() {
let config = AtomicSwapConfig {
network: Network::Testnet,
..Default::default()
};
let manager = AtomicSwapManager::new(config);
manager
.initiate_swap(
"swap1".to_string(),
Amount::from_sat(100000),
vec![0x02; 33],
vec![0x03; 33],
)
.unwrap();
manager.mark_funded("swap1", "txid123".to_string()).unwrap();
manager.refund_swap("swap1").unwrap();
let swap = manager.get_swap("swap1").unwrap();
assert_eq!(swap.status, SwapStatus::Refunded);
}
#[test]
fn test_cancel_swap() {
let config = AtomicSwapConfig {
network: Network::Testnet,
..Default::default()
};
let manager = AtomicSwapManager::new(config);
manager
.initiate_swap(
"swap1".to_string(),
Amount::from_sat(100000),
vec![0x02; 33],
vec![0x03; 33],
)
.unwrap();
manager.cancel_swap("swap1").unwrap();
let swap = manager.get_swap("swap1").unwrap();
assert_eq!(swap.status, SwapStatus::Cancelled);
}
#[test]
fn test_list_swaps() {
let config = AtomicSwapConfig {
network: Network::Testnet,
..Default::default()
};
let manager = AtomicSwapManager::new(config);
manager
.initiate_swap(
"swap1".to_string(),
Amount::from_sat(100000),
vec![0x02; 33],
vec![0x03; 33],
)
.unwrap();
manager
.initiate_swap(
"swap2".to_string(),
Amount::from_sat(200000),
vec![0x02; 33],
vec![0x03; 33],
)
.unwrap();
let swaps = manager.list_swaps();
assert_eq!(swaps.len(), 2);
}
}