diff --git a/CHANGELOG.md b/CHANGELOG.md index 8992f81c..259412ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add the ability to view the swap-cli bitcoin balance and withdraw + See issue https://github.com/comit-network/xmr-btc-swap/issues/694 + ### Fixed - An issue where the connection between ASB and CLI would get closed prematurely. diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index 881aa749..b3f6fb05 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -152,6 +152,61 @@ async fn main() -> Result<()> { println!("{}", table); } + + Command::WithdrawBtc { + bitcoin_electrum_rpc_url, + bitcoin_target_block, + amount, + address, + } => { + cli::tracing::init(debug, json, data_dir.join("logs"), None)?; + let seed = Seed::from_file_or_generate(data_dir.as_path()) + .context("Failed to read in seed file")?; + let bitcoin_wallet = init_bitcoin_wallet( + bitcoin_electrum_rpc_url, + &seed, + data_dir.clone(), + env_config, + bitcoin_target_block, + ) + .await?; + + let amount = match amount { + Some(amount) => amount, + None => { + bitcoin_wallet + .max_giveable(address.script_pubkey().len()) + .await? + } + }; + + let psbt = bitcoin_wallet + .send_to_address(address, amount, None) + .await?; + let signed_tx = bitcoin_wallet.sign_and_finalize(psbt).await?; + + bitcoin_wallet.broadcast(signed_tx, "withdraw").await?; + } + + Command::Balance { + bitcoin_electrum_rpc_url, + bitcoin_target_block, + } => { + cli::tracing::init(debug, json, data_dir.join("logs"), None)?; + let seed = Seed::from_file_or_generate(data_dir.as_path()) + .context("Failed to read in seed file")?; + let bitcoin_wallet = init_bitcoin_wallet( + bitcoin_electrum_rpc_url, + &seed, + data_dir.clone(), + env_config, + bitcoin_target_block, + ) + .await?; + + let bitcoin_balance = bitcoin_wallet.balance().await?; + println!("Bitcoin balance is {}", bitcoin_balance); + } Command::Resume { swap_id, bitcoin_electrum_rpc_url, diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 48e03963..0f0dc18b 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -1,10 +1,12 @@ +use crate::bitcoin::Amount; use crate::env::GetConfig; use crate::fs::system_data_dir; use crate::network::rendezvous::XmrBtcNamespace; use crate::{env, monero}; use anyhow::{Context, Result}; -use bitcoin::AddressType; +use bitcoin::{Address, AddressType}; use libp2p::core::Multiaddr; +use serde::Serialize; use std::ffi::OsString; use std::path::PathBuf; use std::str::FromStr; @@ -107,6 +109,42 @@ where data_dir: data::data_dir_from(data, is_testnet)?, cmd: Command::History, }, + RawCommand::Balance { bitcoin } => { + let (bitcoin_electrum_rpc_url, bitcoin_target_block) = + bitcoin.apply_defaults(is_testnet)?; + + Arguments { + env_config: env_config_from(is_testnet), + debug, + json, + data_dir: data::data_dir_from(data, is_testnet)?, + cmd: Command::Balance { + bitcoin_electrum_rpc_url, + bitcoin_target_block, + }, + } + } + RawCommand::WithdrawBtc { + bitcoin, + amount, + address, + } => { + let (bitcoin_electrum_rpc_url, bitcoin_target_block) = + bitcoin.apply_defaults(is_testnet)?; + + Arguments { + env_config: env_config_from(is_testnet), + debug, + json, + data_dir: data::data_dir_from(data, is_testnet)?, + cmd: Command::WithdrawBtc { + bitcoin_electrum_rpc_url, + bitcoin_target_block, + amount, + address: validate_bitcoin_address(address, is_testnet)?, + }, + } + } RawCommand::Resume { swap_id: SwapId { swap_id }, bitcoin, @@ -204,6 +242,16 @@ pub enum Command { tor_socks5_port: u16, }, History, + WithdrawBtc { + bitcoin_electrum_rpc_url: Url, + bitcoin_target_block: usize, + amount: Option, + address: Address, + }, + Balance { + bitcoin_electrum_rpc_url: Url, + bitcoin_target_block: usize, + }, Resume { swap_id: Uuid, bitcoin_electrum_rpc_url: Url, @@ -296,6 +344,24 @@ enum RawCommand { }, /// Show a list of past, ongoing and completed swaps History, + #[structopt(about = "Allows withdrawing BTC from the internal Bitcoin wallet.")] + WithdrawBtc { + #[structopt(flatten)] + bitcoin: Bitcoin, + + #[structopt( + long = "amount", + help = "Optionally specify the amount of Bitcoin to be withdrawn. If not specified the wallet will be drained." + )] + amount: Option, + #[structopt(long = "address", help = "The address to receive the Bitcoin.")] + address: Address, + }, + #[structopt(about = "Prints the Bitcoin balance.")] + Balance { + #[structopt(flatten)] + bitcoin: Bitcoin, + }, /// Resume a swap Resume { #[structopt(flatten)] @@ -521,6 +587,15 @@ pub struct MoneroAddressNetworkMismatch { actual: monero::Network, } +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Serialize)] +#[error("Invalid Bitcoin address provided, expected address on network {expected:?} but address provided is on {actual:?}")] +pub struct BitcoinAddressNetworkMismatch { + #[serde(with = "crate::bitcoin::network")] + expected: bitcoin::Network, + #[serde(with = "crate::bitcoin::network")] + actual: bitcoin::Network, +} + #[cfg(test)] mod tests { use super::*;