diff --git a/swap/src/cli.rs b/swap/src/cli.rs index 43aa1bd7..6fa8d56b 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -51,6 +51,7 @@ pub enum Command { History, Resume(Resume), Cancel(Cancel), + Refund(Refund), } #[derive(structopt::StructOpt, Debug)] @@ -98,6 +99,24 @@ pub enum Cancel { }, } +#[derive(structopt::StructOpt, Debug)] +pub enum Refund { + BuyXmr { + #[structopt(long = "swap-id")] + swap_id: Uuid, + + // TODO: Remove Alice peer-id/address, it should be saved in the database when running swap + // and loaded from the database when running resume/cancel/refund + #[structopt(long = "counterpart-peer-id")] + alice_peer_id: PeerId, + #[structopt(long = "counterpart-addr")] + alice_addr: Multiaddr, + + #[structopt(flatten)] + config: Config, + }, +} + #[derive(structopt::StructOpt, Debug)] pub struct Config { #[structopt( diff --git a/swap/src/main.rs b/swap/src/main.rs index 08de692b..0b7573d6 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -13,7 +13,7 @@ #![allow(non_snake_case)] use crate::{ - cli::{Cancel, Command, Options, Resume}, + cli::{Cancel, Command, Options, Refund, Resume}, config::{ initial_setup, query_user_for_initial_testnet_config, read_config, ConfigNotInitialized, }, @@ -235,11 +235,49 @@ async fn main() -> Result<()> { tokio::spawn(async move { event_loop.run().await }); match bob::cancel(swap.swap_id, swap.state, swap.bitcoin_wallet, swap.db).await? { - Ok((txid, _)) => { info!("Cancel transaction successfully published with id {}", txid)}, - Err(CancelError::CancelTimelockNotExpiredYet) => {info!("The Cancel Transaction cannot be published yet, because the timelock has not expired. Please try again later.")}, - Err(CancelError::CancelTxAlreadyPublished) => {info!("The Cancel Transaction has already been published.")} + Ok((txid, _)) => { + info!("Cancel transaction successfully published with id {}", txid) + } + Err(CancelError::CancelTimelockNotExpiredYet) => { + info!("The Cancel Transaction cannot be published yet, because the timelock has not expired. Please try again later.") + } + Err(CancelError::CancelTxAlreadyPublished) => { + info!("The Cancel Transaction has already been published.") + } } } + Command::Refund(Refund::BuyXmr { + swap_id, + alice_peer_id, + alice_addr, + config, + }) => { + let (bitcoin_wallet, monero_wallet) = + init_wallets(config.path, bitcoin_network, monero_network).await?; + + // TODO: Optimize to only use the Bitcoin wallet, Monero wallet is unnecessary + let bob_factory = Builder::new( + seed, + db_path, + swap_id, + Arc::new(bitcoin_wallet), + Arc::new(monero_wallet), + alice_addr, + alice_peer_id, + execution_params, + ); + let (swap, event_loop) = bob_factory.build().await?; + + tokio::spawn(async move { event_loop.run().await }); + bob::refund( + swap.swap_id, + swap.state, + swap.execution_params, + swap.bitcoin_wallet, + swap.db, + ) + .await??; + } }; Ok(()) diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index e12ab8b2..4807d16e 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -23,6 +23,7 @@ pub use self::{ cancel::cancel, encrypted_signature::EncryptedSignature, event_loop::{EventLoop, EventLoopHandle}, + refund::refund, state::*, swap::{run, run_until}, swap_request::*, @@ -34,6 +35,7 @@ pub mod cancel; mod encrypted_signature; pub mod event_loop; mod execution_setup; +pub mod refund; pub mod state; pub mod swap; mod swap_request; diff --git a/swap/src/protocol/bob/refund.rs b/swap/src/protocol/bob/refund.rs new file mode 100644 index 00000000..88331516 --- /dev/null +++ b/swap/src/protocol/bob/refund.rs @@ -0,0 +1,39 @@ +use crate::{ + bitcoin::Wallet, + database::{Database, Swap}, + execution_params::ExecutionParams, + protocol::bob::BobState, +}; +use anyhow::Result; +use std::sync::Arc; +use uuid::Uuid; + +#[derive(thiserror::Error, Debug, Clone, Copy)] +#[error("Cannot refund because swap {0} was not cancelled yet. Make sure to cancel the swap before trying to refund.")] +pub struct SwapNotCancelledYet(Uuid); + +pub async fn refund( + swap_id: Uuid, + state: BobState, + execution_params: ExecutionParams, + bitcoin_wallet: Arc, + db: Database, +) -> Result> { + let state4 = match state { + BobState::BtcCancelled(state4) => state4, + _ => { + return Ok(Err(SwapNotCancelledYet(swap_id))); + } + }; + + state4 + .refund_btc(bitcoin_wallet.as_ref(), execution_params) + .await?; + + let state = BobState::BtcRefunded(state4); + let db_state = state.clone().into(); + + db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; + + Ok(Ok(state)) +}