mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Adjust quote based on Bitcoin balance
This commit is contained in:
parent
6911509b16
commit
d224c2910b
@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Revert logs to use rfc3339 local time formatting.
|
- Revert logs to use rfc3339 local time formatting.
|
||||||
- Always write logs as JSON to files
|
- Always write logs as JSON to files
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
## [0.10.2] - 2021-12-25
|
## [0.10.2] - 2021-12-25
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@ -305,7 +310,7 @@ It is possible to migrate critical data from the old db to the sqlite but there
|
|||||||
- Fixed an issue where Alice would not verify if Bob's Bitcoin lock transaction is semantically correct, i.e. pays the agreed upon amount to an output owned by both of them.
|
- Fixed an issue where Alice would not verify if Bob's Bitcoin lock transaction is semantically correct, i.e. pays the agreed upon amount to an output owned by both of them.
|
||||||
Fixing this required a **breaking change** on the network layer and hence old versions are not compatible with this version.
|
Fixing this required a **breaking change** on the network layer and hence old versions are not compatible with this version.
|
||||||
|
|
||||||
[Unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.2...HEAD
|
[unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.2...HEAD
|
||||||
[0.10.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.1...0.10.2
|
[0.10.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.1...0.10.2
|
||||||
[0.10.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.0...0.10.1
|
[0.10.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.0...0.10.1
|
||||||
[0.10.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.9.0...0.10.0
|
[0.10.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.9.0...0.10.0
|
||||||
|
@ -319,13 +319,46 @@ where
|
|||||||
min_buy: bitcoin::Amount,
|
min_buy: bitcoin::Amount,
|
||||||
max_buy: bitcoin::Amount,
|
max_buy: bitcoin::Amount,
|
||||||
) -> Result<BidQuote> {
|
) -> Result<BidQuote> {
|
||||||
let rate = self
|
let ask_price = self
|
||||||
.latest_rate
|
.latest_rate
|
||||||
.latest_rate()
|
.latest_rate()
|
||||||
.context("Failed to get latest rate")?;
|
.context("Failed to get latest rate")?
|
||||||
|
.ask()
|
||||||
|
.context("Failed to compute asking price")?;
|
||||||
|
|
||||||
|
let max_bitcoin_for_monero = self
|
||||||
|
.monero_wallet
|
||||||
|
.get_balance()
|
||||||
|
.await?
|
||||||
|
.max_bitcoin_for_price(ask_price);
|
||||||
|
|
||||||
|
if min_buy > max_bitcoin_for_monero {
|
||||||
|
tracing::warn!(
|
||||||
|
"Your Monero balance is too low to initiate a swap, as your minimum swap amount is {}. You could at most swap {}",
|
||||||
|
min_buy, max_bitcoin_for_monero
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(BidQuote {
|
||||||
|
price: ask_price,
|
||||||
|
min_quantity: bitcoin::Amount::ZERO,
|
||||||
|
max_quantity: bitcoin::Amount::ZERO,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if max_buy > max_bitcoin_for_monero {
|
||||||
|
tracing::warn!(
|
||||||
|
"Your Monero balance is too low to initiate a swap with the maximum swap amount {} that you have specified in your config. You can at most swap {}",
|
||||||
|
max_buy, max_bitcoin_for_monero
|
||||||
|
);
|
||||||
|
return Ok(BidQuote {
|
||||||
|
price: ask_price,
|
||||||
|
min_quantity: min_buy,
|
||||||
|
max_quantity: max_bitcoin_for_monero,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(BidQuote {
|
Ok(BidQuote {
|
||||||
price: rate.ask().context("Failed to compute asking price")?,
|
price: ask_price,
|
||||||
min_quantity: min_buy,
|
min_quantity: min_buy,
|
||||||
max_quantity: max_buy,
|
max_quantity: max_buy,
|
||||||
})
|
})
|
||||||
|
@ -29,7 +29,7 @@ use swap::cli::{list_sellers, EventLoop, SellerStatus};
|
|||||||
use swap::database::open_db;
|
use swap::database::open_db;
|
||||||
use swap::env::Config;
|
use swap::env::Config;
|
||||||
use swap::libp2p_ext::MultiAddrExt;
|
use swap::libp2p_ext::MultiAddrExt;
|
||||||
use swap::network::quote::BidQuote;
|
use swap::network::quote::{BidQuote, ZeroQuoteReceived};
|
||||||
use swap::network::swarm;
|
use swap::network::swarm;
|
||||||
use swap::protocol::bob;
|
use swap::protocol::bob;
|
||||||
use swap::protocol::bob::{BobState, Swap};
|
use swap::protocol::bob::{BobState, Swap};
|
||||||
@ -99,7 +99,7 @@ async fn main() -> Result<()> {
|
|||||||
let event_loop = tokio::spawn(event_loop.run());
|
let event_loop = tokio::spawn(event_loop.run());
|
||||||
|
|
||||||
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size());
|
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size());
|
||||||
let (amount, fees) = determine_btc_to_swap(
|
let (amount, fees) = match determine_btc_to_swap(
|
||||||
json,
|
json,
|
||||||
event_loop_handle.request_quote(),
|
event_loop_handle.request_quote(),
|
||||||
bitcoin_wallet.new_address(),
|
bitcoin_wallet.new_address(),
|
||||||
@ -107,7 +107,16 @@ async fn main() -> Result<()> {
|
|||||||
max_givable,
|
max_givable,
|
||||||
|| bitcoin_wallet.sync(),
|
|| bitcoin_wallet.sync(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await
|
||||||
|
{
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(error) => match error.downcast::<ZeroQuoteReceived>() {
|
||||||
|
Ok(_) => {
|
||||||
|
bail!("Seller's XMR balance is currently too low to initiate a swap, please try again later")
|
||||||
|
}
|
||||||
|
Err(other) => bail!(other),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
tracing::info!(%amount, %fees, "Determined swap amount");
|
tracing::info!(%amount, %fees, "Determined swap amount");
|
||||||
|
|
||||||
@ -556,6 +565,11 @@ where
|
|||||||
{
|
{
|
||||||
tracing::debug!("Requesting quote");
|
tracing::debug!("Requesting quote");
|
||||||
let bid_quote = bid_quote.await?;
|
let bid_quote = bid_quote.await?;
|
||||||
|
|
||||||
|
if bid_quote.max_quantity == bitcoin::Amount::ZERO {
|
||||||
|
bail!(ZeroQuoteReceived)
|
||||||
|
}
|
||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
price = %bid_quote.price,
|
price = %bid_quote.price,
|
||||||
minimum_amount = %bid_quote.min_quantity,
|
minimum_amount = %bid_quote.min_quantity,
|
||||||
@ -915,6 +929,32 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_bid_quote_max_amount_0_return_errorq() {
|
||||||
|
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
|
||||||
|
Amount::from_btc(0.0001).unwrap(),
|
||||||
|
Amount::from_btc(0.01).unwrap(),
|
||||||
|
])));
|
||||||
|
|
||||||
|
let determination_error = determine_btc_to_swap(
|
||||||
|
true,
|
||||||
|
async { Ok(quote_with_max(0.00)) },
|
||||||
|
get_dummy_address(),
|
||||||
|
|| async { Ok(Amount::from_btc(0.0101)?) },
|
||||||
|
|| async {
|
||||||
|
let mut result = givable.lock().unwrap();
|
||||||
|
result.give()
|
||||||
|
},
|
||||||
|
|| async { Ok(()) },
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
assert_eq!("Received quote of 0", determination_error);
|
||||||
|
}
|
||||||
|
|
||||||
struct MaxGiveable {
|
struct MaxGiveable {
|
||||||
amounts: Vec<Amount>,
|
amounts: Vec<Amount>,
|
||||||
call_counter: usize,
|
call_counter: usize,
|
||||||
|
@ -99,6 +99,20 @@ impl Amount {
|
|||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn max_bitcoin_for_price(&self, ask_price: bitcoin::Amount) -> bitcoin::Amount {
|
||||||
|
let piconero_minus_fee = self.as_piconero().saturating_sub(MONERO_FEE.as_piconero());
|
||||||
|
|
||||||
|
if piconero_minus_fee == 0 {
|
||||||
|
return bitcoin::Amount::ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There needs to be an offset for difference in zeroes beetween Piconeros and
|
||||||
|
// Satoshis
|
||||||
|
let piconero_calc = (piconero_minus_fee * ask_price.as_sat()) / PICONERO_OFFSET;
|
||||||
|
|
||||||
|
bitcoin::Amount::from_sat(piconero_calc)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_monero(amount: f64) -> Result<Self> {
|
pub fn from_monero(amount: f64) -> Result<Self> {
|
||||||
let decimal = Decimal::try_from(amount)?;
|
let decimal = Decimal::try_from(amount)?;
|
||||||
Self::from_decimal(decimal)
|
Self::from_decimal(decimal)
|
||||||
@ -360,6 +374,30 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn geting_max_bitcoin_to_trade() {
|
||||||
|
let amount = Amount::parse_monero("10").unwrap();
|
||||||
|
let bitcoin_price_sats = bitcoin::Amount::from_sat(382_900);
|
||||||
|
|
||||||
|
let monero_max_from_bitcoin = amount.max_bitcoin_for_price(bitcoin_price_sats);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
bitcoin::Amount::from_sat(3_828_988),
|
||||||
|
monero_max_from_bitcoin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn geting_max_bitcoin_to_trade_with_balance_smaller_than_locking_fee() {
|
||||||
|
let monero = "0.00001";
|
||||||
|
let amount = Amount::parse_monero(monero).unwrap();
|
||||||
|
let bitcoin_price_sats = bitcoin::Amount::from_sat(382_900);
|
||||||
|
|
||||||
|
let monero_max_from_bitcoin = amount.max_bitcoin_for_price(bitcoin_price_sats);
|
||||||
|
|
||||||
|
assert_eq!(bitcoin::Amount::ZERO, monero_max_from_bitcoin);
|
||||||
|
}
|
||||||
|
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -37,6 +37,10 @@ pub struct BidQuote {
|
|||||||
pub max_quantity: bitcoin::Amount,
|
pub max_quantity: bitcoin::Amount,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, thiserror::Error)]
|
||||||
|
#[error("Received quote of 0")]
|
||||||
|
pub struct ZeroQuoteReceived;
|
||||||
|
|
||||||
/// Constructs a new instance of the `quote` behaviour to be used by the ASB.
|
/// Constructs a new instance of the `quote` behaviour to be used by the ASB.
|
||||||
///
|
///
|
||||||
/// The ASB is always listening and only supports inbound connections, i.e.
|
/// The ASB is always listening and only supports inbound connections, i.e.
|
||||||
|
Loading…
Reference in New Issue
Block a user