diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a5f8cec..41243c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,9 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Adjust quote based on Bitcoin balance. - If the max_buy_btc in the ASB config is higher than the available balance to trade it will return the max available balance discounting the locking fees for monero, in the case the balance is lower than the min_buy_btc config it will return 0 to the CLI. If the ASB returns a quote of 0 the CLI will not allow you continue with a trade. + If the max_buy_btc in the ASB config is higher than the available balance to trade, it will return the max available balance discounting the Monero locking fees. In the case the balance is lower than the min_buy_btc config it will return 0 to the CLI. If the ASB returns a quote of 0 the CLI will not allow you continue with a trade. - Reduce required confirmations for Bitcoin transactions from 2 to 1 - Both the ASB and CLI now support the [Identify](https://github.com/libp2p/specs/blob/master/identify/README.md) protocol. This makes its version and network (testnet/mainnet) avaliable to others +- Display minimum BTC deposit required to cover the minimum quantity plus fee in the Swap CLI ## [0.10.2] - 2021-12-25 diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index a1188759..2acfb61a 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -105,6 +105,8 @@ async fn main() -> Result<()> { let event_loop = tokio::spawn(event_loop.run()); let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size()); + let estimate_fee = |amount| bitcoin_wallet.estimate_fee(TxLock::weight(), amount); + let (amount, fees) = match determine_btc_to_swap( json, event_loop_handle.request_quote(), @@ -112,6 +114,7 @@ async fn main() -> Result<()> { || bitcoin_wallet.balance(), max_givable, || bitcoin_wallet.sync(), + estimate_fee, ) .await { @@ -558,13 +561,14 @@ fn qr_code(value: &impl ToString) -> Result { Ok(qr_code) } -async fn determine_btc_to_swap( +async fn determine_btc_to_swap( json: bool, bid_quote: impl Future>, get_new_address: impl Future>, balance: FB, max_giveable_fn: FMG, sync: FS, + estimate_fee: FFE, ) -> Result<(bitcoin::Amount, bitcoin::Amount)> where TB: Future>, @@ -573,6 +577,8 @@ where FMG: Fn() -> TMG, TS: Future>, FS: Fn() -> TS, + FFE: Fn(bitcoin::Amount) -> TFE, + TFE: Future>, { tracing::debug!("Requesting quote"); let bid_quote = bid_quote.await?; @@ -600,8 +606,17 @@ where } loop { + let min_outstanding = bid_quote.min_quantity - max_giveable; + let min_fee = estimate_fee(min_outstanding).await?; + let min_deposit = min_outstanding + min_fee; + + tracing::info!( + "Deposit at least {} to cover the min quantity with fee!", + min_deposit + ); tracing::info!( %deposit_address, + %min_deposit, %max_giveable, %minimum_amount, %maximum_amount, @@ -633,9 +648,7 @@ where let balance = balance().await?; let fees = balance - max_giveable; - let max_accepted = bid_quote.max_quantity; - let btc_swap_amount = min(max_giveable, max_accepted); Ok((btc_swap_amount, fees)) @@ -668,6 +681,7 @@ mod tests { result.give() }, || async { Ok(()) }, + |_| async { Ok(Amount::from_sat(1000)) }, ) .await .unwrap(); @@ -679,7 +693,8 @@ mod tests { assert_eq!( writer.captured(), r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC + INFO swap: Deposit at least 0.00001000 BTC to cover the min quantity with fee! + INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC INFO swap: Received Bitcoin new_balance=0.00100000 BTC max_giveable=0.00090000 BTC " ); @@ -703,6 +718,7 @@ mod tests { result.give() }, || async { Ok(()) }, + |_| async { Ok(Amount::from_sat(1000)) }, ) .await .unwrap(); @@ -714,14 +730,15 @@ mod tests { assert_eq!( writer.captured(), r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC + INFO swap: Deposit at least 0.00001000 BTC to cover the min quantity with fee! + INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC INFO swap: Received Bitcoin new_balance=0.10010000 BTC max_giveable=0.10000000 BTC " ); } #[tokio::test] - async fn given_initial_balance_below_max_quantity_swaps_max_givable() { + async fn given_initial_balance_below_max_quantity_swaps_max_giveable() { let writer = capture_logs(LevelFilter::INFO); let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![ Amount::from_btc(0.0049).unwrap(), @@ -738,6 +755,7 @@ mod tests { result.give() }, || async { Ok(()) }, + |_| async { Ok(Amount::from_sat(1000)) }, ) .await .unwrap(); @@ -748,8 +766,7 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC -" + " INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC\n" ); } @@ -771,6 +788,7 @@ mod tests { result.give() }, || async { Ok(()) }, + |_| async { Ok(Amount::from_sat(1000)) }, ) .await .unwrap(); @@ -781,8 +799,7 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC -" + " INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC\n" ); } @@ -804,6 +821,7 @@ mod tests { result.give() }, || async { Ok(()) }, + |_| async { Ok(Amount::from_sat(1000)) }, ) .await .unwrap(); @@ -815,7 +833,8 @@ mod tests { assert_eq!( writer.captured(), r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC + INFO swap: Deposit at least 0.01001000 BTC to cover the min quantity with fee! + INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.01001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC " ); @@ -839,6 +858,7 @@ mod tests { result.give() }, || async { Ok(()) }, + |_| async { Ok(Amount::from_sat(1000)) }, ) .await .unwrap(); @@ -850,7 +870,8 @@ mod tests { assert_eq!( writer.captured(), r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00010000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC + INFO swap: Deposit at least 0.00991000 BTC to cover the min quantity with fee! + INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00991000 BTC max_giveable=0.00010000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC " ); @@ -879,6 +900,7 @@ mod tests { result.give() }, || async { Ok(()) }, + |_| async { Ok(Amount::from_sat(1000)) }, ), ) .await @@ -888,10 +910,12 @@ mod tests { assert_eq!( writer.captured(), r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC + INFO swap: Deposit at least 0.10001000 BTC to cover the min quantity with fee! + INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC INFO swap: Deposited amount is less than `min_quantity` - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.01000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC + INFO swap: Deposit at least 0.09001000 BTC to cover the min quantity with fee! + INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.09001000 BTC max_giveable=0.01000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC " ); } @@ -925,6 +949,7 @@ mod tests { result.give() }, || async { Ok(()) }, + |_| async { Ok(Amount::from_sat(1000)) }, ), ) .await @@ -934,14 +959,15 @@ mod tests { assert_eq!( writer.captured(), r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC + INFO swap: Deposit at least 0.10001000 BTC to cover the min quantity with fee! + INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC INFO swap: Received Bitcoin new_balance=0.21000000 BTC max_giveable=0.20000000 BTC " ); } #[tokio::test] - async fn given_bid_quote_max_amount_0_return_errorq() { + async fn given_bid_quote_max_amount_0_return_error() { let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![ Amount::from_btc(0.0001).unwrap(), Amount::from_btc(0.01).unwrap(), @@ -957,6 +983,7 @@ mod tests { result.give() }, || async { Ok(()) }, + |_| async { Ok(Amount::from_sat(1000)) }, ) .await .err() diff --git a/swap/src/bitcoin/lock.rs b/swap/src/bitcoin/lock.rs index c9efff2b..2f195ff7 100644 --- a/swap/src/bitcoin/lock.rs +++ b/swap/src/bitcoin/lock.rs @@ -11,6 +11,7 @@ use bitcoin::Script; use serde::{Deserialize, Serialize}; const SCRIPT_SIZE: usize = 34; +const TX_LOCK_WEIGHT: usize = 485; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct TxLock { @@ -161,6 +162,10 @@ impl TxLock { output: vec![tx_out], } } + + pub fn weight() -> usize { + TX_LOCK_WEIGHT + } } impl From for PartiallySignedTransaction { diff --git a/swap/src/cli/tracing.rs b/swap/src/cli/tracing.rs index e759e0d0..a1cd77fb 100644 --- a/swap/src/cli/tracing.rs +++ b/swap/src/cli/tracing.rs @@ -16,7 +16,8 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef, swap_id: Option, swap_id: Option, swap_id: Option {