diff --git a/swap-asb/src/command.rs b/swap-asb/src/command.rs index 11ffa558..a535b796 100644 --- a/swap-asb/src/command.rs +++ b/swap-asb/src/command.rs @@ -161,6 +161,16 @@ where env_config: env_config(testnet), cmd: Command::Punish { swap_id }, }, + RawCommand::ManualRecovery(ManualRecovery::ExportMoneroLockWallet { swap_id }) => { + Arguments { + testnet, + json, + trace, + config_path: config_path(config, testnet)?, + env_config: env_config(testnet), + cmd: Command::ExportMoneroLockWallet { swap_id }, + } + } RawCommand::ManualRecovery(ManualRecovery::SafelyAbort { swap_id }) => Arguments { testnet, json, @@ -252,6 +262,9 @@ pub enum Command { }, ExportBitcoinWallet, ExportMoneroWallet, + ExportMoneroLockWallet { + swap_id: Uuid, + }, } #[derive(structopt::StructOpt, Debug)] @@ -391,6 +404,16 @@ pub enum ManualRecovery { #[structopt(flatten)] punish_params: RecoverCommandParams, }, + #[structopt( + about = "Get the keys to the Monero lock wallet of a specific swap (requires the taker to have refunded first)." + )] + ExportMoneroLockWallet { + #[structopt( + long = "swap-id", + help = "The swap id can be retrieved using the history subcommand" + )] + swap_id: Uuid, + }, #[structopt(about = "Safely Abort requires the swap to be in a state prior to locking XMR.")] SafelyAbort { #[structopt( diff --git a/swap-asb/src/main.rs b/swap-asb/src/main.rs index fd010b21..274854e3 100644 --- a/swap-asb/src/main.rs +++ b/swap-asb/src/main.rs @@ -505,6 +505,57 @@ pub async fn main() -> Result<()> { println!("Seed : {seed}"); println!("Restore height: {creation_height}"); } + Command::ExportMoneroLockWallet { swap_id } => { + let db = open_db(db_file, AccessMode::ReadWrite, None).await?; + let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config, false).await?; + + let swap_states = db + .get_states(swap_id) + .await + .context(format!("Error querying database for swap {swap_id}"))?; + + if swap_states.is_empty() { + tracing::error!("No state save for this swap in the database"); + } + + tracing::info!(?swap_states, "Found swap states"); + + let state3 = swap_states + .iter() + .filter_map(|state| match state { + State::Alice(AliceState::Started { state3 }) + | State::Alice(AliceState::BtcLocked { state3 }) + | State::Alice(AliceState::BtcLockTransactionSeen { state3 }) => { + Some(state3.clone()) + } + _ => None, + }) + .next() + .context("Couldn't find state Started for this swap")?; + + let secret_spend_key = match state3.watch_for_btc_tx_refund(&bitcoin_wallet).await { + Ok(secret) => secret, + Err(error) => { + tracing::error!( + ?error, + "Could not extract refund secret from taker's refund transaction" + ); + return Ok(()); + } + }; + let secret_view_key = state3.v; + let primary_address = { + let public_spend_key = monero::PublicKey::from_private_key(&secret_spend_key); + let public_view_key = monero::PublicKey::from_private_key(&secret_view_key.into()); + + monero::Address::standard(config.monero.network, public_spend_key, public_view_key) + }; + + println!("Retrieved the refund secret from taker's refund transaction. Below are the keys to the Monero lock wallet: +private spend key: {secret_spend_key} +private view key: {secret_view_key} +primary address: {primary_address}"); + } } Ok(())