Introduce a minimum buy amount

Introduces a minimum buy Bitcoin amount similar to the maximum amount already present.
For the CLI the minimum amount is enforced by waiting until at least the minimum is available as max-giveable amount.
This commit is contained in:
Daniel Karzel 2021-05-07 19:06:58 +10:00
parent 6d3cf0af91
commit 652aae9590
No known key found for this signature in database
GPG key ID: 30C3FC2E438ADB6E
12 changed files with 319 additions and 81 deletions

View file

@ -20,6 +20,7 @@ const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://electrum.blockstream.info:60002";
const DEFAULT_MONERO_WALLET_RPC_TESTNET_URL: &str = "http://127.0.0.1:38083/json_rpc";
const DEFAULT_BITCOIN_CONFIRMATION_TARGET: usize = 3;
const DEFAULT_MIN_BUY_AMOUNT: f64 = 0.002f64;
const DEFAULT_MAX_BUY_AMOUNT: f64 = 0.02f64;
const DEFAULT_SPREAD: f64 = 0.02f64;
@ -81,6 +82,8 @@ pub struct TorConf {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Maker {
#[serde(with = "::bitcoin::util::amount::serde::as_btc")]
pub min_buy_btc: bitcoin::Amount,
#[serde(with = "::bitcoin::util::amount::serde::as_btc")]
pub max_buy_btc: bitcoin::Amount,
pub ask_spread: Decimal,
@ -200,6 +203,12 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
.default(DEFAULT_SOCKS5_PORT.to_owned())
.interact_text()?;
let min_buy = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter minimum Bitcoin amount you are willing to accept per swap or hit enter to use default.")
.default(DEFAULT_MIN_BUY_AMOUNT)
.interact_text()?;
let min_buy = bitcoin::Amount::from_btc(min_buy)?;
let max_buy = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter maximum Bitcoin amount you are willing to accept per swap or hit enter to use default.")
.default(DEFAULT_MAX_BUY_AMOUNT)
@ -234,6 +243,7 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
socks5_port: tor_socks5_port,
},
maker: Maker {
min_buy_btc: min_buy,
max_buy_btc: max_buy,
ask_spread,
},
@ -271,6 +281,7 @@ mod tests {
},
tor: Default::default(),
maker: Maker {
min_buy_btc: bitcoin::Amount::from_btc(DEFAULT_MIN_BUY_AMOUNT).unwrap(),
max_buy_btc: bitcoin::Amount::from_btc(DEFAULT_MAX_BUY_AMOUNT).unwrap(),
ask_spread: Decimal::from_f64(DEFAULT_SPREAD).unwrap(),
},

View file

@ -123,6 +123,7 @@ async fn main() -> Result<()> {
&seed,
current_balance,
lock_fee,
config.maker.min_buy_btc,
config.maker.max_buy_btc,
kraken_rate.clone(),
resume_only,
@ -140,6 +141,7 @@ async fn main() -> Result<()> {
Arc::new(monero_wallet),
Arc::new(db),
kraken_rate,
config.maker.min_buy_btc,
config.maker.max_buy_btc,
)
.unwrap();

View file

@ -20,7 +20,7 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use structopt::StructOpt;
use swap::bitcoin::{Amount, TxLock};
use swap::bitcoin::TxLock;
use swap::cli::command::{Arguments, Command, MoneroParams};
use swap::database::Database;
use swap::env::{Config, GetConfig};
@ -94,23 +94,19 @@ async fn main() -> Result<()> {
EventLoop::new(swap_id, swarm, alice_peer_id, bitcoin_wallet.clone())?;
let event_loop = tokio::spawn(event_loop.run());
let send_bitcoin = determine_btc_to_swap(
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size());
let (send_bitcoin, fees) = determine_btc_to_swap(
event_loop_handle.request_quote(),
bitcoin_wallet.balance(),
max_givable().await?,
bitcoin_wallet.new_address(),
async {
while bitcoin_wallet.balance().await? == Amount::ZERO {
bitcoin_wallet.sync().await?;
tokio::time::sleep(Duration::from_secs(1)).await;
}
bitcoin_wallet.balance().await
},
bitcoin_wallet.max_giveable(TxLock::script_size()),
|| bitcoin_wallet.balance(),
max_givable,
|| bitcoin_wallet.sync(),
)
.await?;
info!("Swapping {} with {} fees", send_bitcoin, fees);
db.insert_peer_id(swap_id, alice_peer_id).await?;
let swap = Swap::new(
@ -332,51 +328,83 @@ async fn init_monero_wallet(
Ok((monero_wallet, monero_wallet_rpc_process))
}
async fn determine_btc_to_swap(
request_quote: impl Future<Output = Result<BidQuote>>,
initial_balance: impl Future<Output = Result<bitcoin::Amount>>,
async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS>(
bid_quote: impl Future<Output = Result<BidQuote>>,
mut current_maximum_giveable: bitcoin::Amount,
get_new_address: impl Future<Output = Result<bitcoin::Address>>,
wait_for_deposit: impl Future<Output = Result<bitcoin::Amount>>,
max_giveable: impl Future<Output = Result<bitcoin::Amount>>,
) -> Result<bitcoin::Amount> {
balance: FB,
max_giveable: FMG,
sync: FS,
) -> Result<(bitcoin::Amount, bitcoin::Amount)>
where
TB: Future<Output = Result<bitcoin::Amount>>,
FB: Fn() -> TB,
TMG: Future<Output = Result<bitcoin::Amount>>,
FMG: Fn() -> TMG,
TS: Future<Output = Result<()>>,
FS: Fn() -> TS,
{
debug!("Requesting quote");
let bid_quote = request_quote.await?;
let bid_quote = bid_quote.await?;
info!("Received quote: 1 XMR ~ {}", bid_quote.price);
// TODO: Also wait for more funds if balance < dust
let initial_balance = initial_balance.await?;
let max_giveable = if current_maximum_giveable == bitcoin::Amount::ZERO
|| current_maximum_giveable < bid_quote.min_quantity
{
let deposit_address = get_new_address.await?;
let minimum_amount = bid_quote.min_quantity;
let maximum_amount = bid_quote.max_quantity;
let balance = if initial_balance == Amount::ZERO {
info!(
"Please deposit the BTC you want to swap to {} (max {})",
get_new_address.await?,
bid_quote.max_quantity
%deposit_address,
%current_maximum_giveable,
%minimum_amount,
%maximum_amount,
"Please deposit BTC you want to swap to",
);
let new_balance = wait_for_deposit
.await
.context("Failed to wait for Bitcoin deposit")?;
loop {
sync().await?;
info!("Received {}", new_balance);
new_balance
let new_max_givable = max_giveable().await?;
if new_max_givable != current_maximum_giveable {
current_maximum_giveable = new_max_givable;
let new_balance = balance().await?;
tracing::info!(
%new_balance,
%current_maximum_giveable,
"Received BTC",
);
if current_maximum_giveable >= bid_quote.min_quantity {
break;
} else {
tracing::info!(
%minimum_amount,
%deposit_address,
"Please deposit more, not enough BTC to trigger swap with",
);
}
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
current_maximum_giveable
} else {
info!("Found {} in wallet", initial_balance);
initial_balance
current_maximum_giveable
};
let max_giveable = max_giveable
.await
.context("Failed to compute max 'giveable' Bitcoin amount")?;
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);
info!("Swapping {} with {} fees", btc_swap_amount, fees);
Ok(btc_swap_amount)
Ok((btc_swap_amount, fees))
}
#[cfg(test)]
@ -390,74 +418,162 @@ mod tests {
async fn given_no_balance_and_transfers_less_than_max_swaps_max_giveable() {
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let amount = determine_btc_to_swap(
let (amount, fees) = determine_btc_to_swap(
async { Ok(quote_with_max(0.01)) },
async { Ok(Amount::ZERO) },
Amount::ZERO,
get_dummy_address(),
async { Ok(Amount::from_btc(0.0001)?) },
async { Ok(Amount::from_btc(0.00009)?) },
|| async { Ok(Amount::from_btc(0.001)?) },
|| async { Ok(Amount::from_btc(0.0009)?) },
|| async { Ok(()) },
)
.await
.unwrap();
assert_eq!(amount, Amount::from_btc(0.00009).unwrap())
let expected_amount = Amount::from_btc(0.0009).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_no_balance_and_transfers_more_then_swaps_max_quantity_from_quote() {
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let amount = determine_btc_to_swap(
let (amount, fees) = determine_btc_to_swap(
async { Ok(quote_with_max(0.01)) },
async { Ok(Amount::ZERO) },
Amount::ZERO,
get_dummy_address(),
async { Ok(Amount::from_btc(0.1)?) },
async { Ok(Amount::from_btc(0.09)?) },
|| async { Ok(Amount::from_btc(0.1001)?) },
|| async { Ok(Amount::from_btc(0.1)?) },
|| async { Ok(()) },
)
.await
.unwrap();
assert_eq!(amount, Amount::from_btc(0.01).unwrap())
let expected_amount = Amount::from_btc(0.01).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_initial_balance_below_max_quantity_swaps_max_givable() {
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let amount = determine_btc_to_swap(
let (amount, fees) = determine_btc_to_swap(
async { Ok(quote_with_max(0.01)) },
async { Ok(Amount::from_btc(0.005)?) },
Amount::from_btc(0.0049).unwrap(),
async { panic!("should not request new address when initial balance is > 0") },
async { panic!("should not wait for deposit when initial balance > 0") },
async { Ok(Amount::from_btc(0.0049)?) },
|| async { Ok(Amount::from_btc(0.005)?) },
|| async { panic!("should not wait for deposit when initial balance > 0") },
|| async { Ok(()) },
)
.await
.unwrap();
assert_eq!(amount, Amount::from_btc(0.0049).unwrap())
let expected_amount = Amount::from_btc(0.0049).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_initial_balance_above_max_quantity_swaps_max_quantity() {
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let amount = determine_btc_to_swap(
let (amount, fees) = determine_btc_to_swap(
async { Ok(quote_with_max(0.01)) },
async { Ok(Amount::from_btc(0.1)?) },
Amount::from_btc(0.1).unwrap(),
async { panic!("should not request new address when initial balance is > 0") },
async { panic!("should not wait for deposit when initial balance > 0") },
async { Ok(Amount::from_btc(0.09)?) },
|| async { Ok(Amount::from_btc(0.1001)?) },
|| async { panic!("should not wait for deposit when initial balance > 0") },
|| async { Ok(()) },
)
.await
.unwrap();
assert_eq!(amount, Amount::from_btc(0.01).unwrap())
let expected_amount = Amount::from_btc(0.01).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_no_initial_balance_then_min_wait_for_sufficient_deposit() {
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let (amount, fees) = determine_btc_to_swap(
async { Ok(quote_with_min(0.01)) },
Amount::ZERO,
get_dummy_address(),
|| async { Ok(Amount::from_btc(0.0101)?) },
|| async { Ok(Amount::from_btc(0.01)?) },
|| async { Ok(()) },
)
.await
.unwrap();
let expected_amount = Amount::from_btc(0.01).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_balance_less_then_min_wait_for_sufficient_deposit() {
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let (amount, fees) = determine_btc_to_swap(
async { Ok(quote_with_min(0.01)) },
Amount::from_btc(0.0001).unwrap(),
get_dummy_address(),
|| async { Ok(Amount::from_btc(0.0101)?) },
|| async { Ok(Amount::from_btc(0.01)?) },
|| async { Ok(()) },
)
.await
.unwrap();
let expected_amount = Amount::from_btc(0.01).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_no_initial_balance_and_transfers_less_than_min_keep_waiting() {
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let error = tokio::time::timeout(
Duration::from_secs(1),
determine_btc_to_swap(
async { Ok(quote_with_min(0.1)) },
Amount::ZERO,
get_dummy_address(),
|| async { Ok(Amount::from_btc(0.0101)?) },
|| async { Ok(Amount::from_btc(0.01)?) },
|| async { Ok(()) },
),
)
.await
.unwrap_err();
assert!(matches!(error, tokio::time::error::Elapsed { .. }))
}
fn quote_with_max(btc: f64) -> BidQuote {
BidQuote {
price: Amount::from_btc(0.001).unwrap(),
max_quantity: Amount::from_btc(btc).unwrap(),
min_quantity: Amount::ZERO,
}
}
fn quote_with_min(btc: f64) -> BidQuote {
BidQuote {
price: Amount::from_btc(0.001).unwrap(),
max_quantity: Amount::max_value(),
min_quantity: Amount::from_btc(btc).unwrap(),
}
}

View file

@ -30,6 +30,9 @@ pub struct BidQuote {
/// The price at which the maker is willing to buy at.
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
pub price: bitcoin::Amount,
/// The minimum quantity the maker is willing to buy.
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
pub min_quantity: bitcoin::Amount,
/// The maximum quantity the maker is willing to buy.
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
pub max_quantity: bitcoin::Amount,

View file

@ -43,7 +43,13 @@ pub enum Response {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Error {
NoSwapsAccepted,
MaxBuyAmountExceeded {
AmountBelowMinimum {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
min: bitcoin::Amount,
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
buy: bitcoin::Amount,
},
AmountAboveMaximum {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
max: bitcoin::Amount,
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
@ -74,8 +80,16 @@ mod tests {
let serialized = serde_json::to_string(&Response::Error(Error::NoSwapsAccepted)).unwrap();
assert_eq!(error, serialized);
let error = r#"{"Error":{"MaxBuyAmountExceeded":{"max":0,"buy":0}}}"#.to_string();
let serialized = serde_json::to_string(&Response::Error(Error::MaxBuyAmountExceeded {
let error = r#"{"Error":{"AmountBelowMinimum":{"min":0,"buy":0}}}"#.to_string();
let serialized = serde_json::to_string(&Response::Error(Error::AmountBelowMinimum {
min: Default::default(),
buy: Default::default(),
}))
.unwrap();
assert_eq!(error, serialized);
let error = r#"{"Error":{"AmountAboveMaximum":{"max":0,"buy":0}}}"#.to_string();
let serialized = serde_json::to_string(&Response::Error(Error::AmountAboveMaximum {
max: Default::default(),
buy: Default::default(),
}))

View file

@ -12,6 +12,7 @@ pub fn alice<LR>(
seed: &Seed,
balance: monero::Amount,
lock_fee: monero::Amount,
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
latest_rate: LR,
resume_only: bool,
@ -21,7 +22,14 @@ where
{
with_clear_net(
seed,
alice::Behaviour::new(balance, lock_fee, max_buy, latest_rate, resume_only),
alice::Behaviour::new(
balance,
lock_fee,
min_buy,
max_buy,
latest_rate,
resume_only,
),
)
}

View file

@ -84,6 +84,7 @@ where
pub fn new(
balance: monero::Amount,
lock_fee: monero::Amount,
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
latest_rate: LR,
resume_only: bool,
@ -93,6 +94,7 @@ where
spot_price: spot_price::Behaviour::new(
balance,
lock_fee,
min_buy,
max_buy,
latest_rate,
resume_only,

View file

@ -43,6 +43,7 @@ where
monero_wallet: Arc<monero::Wallet>,
db: Arc<Database>,
latest_rate: LR,
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
swap_sender: mpsc::Sender<Swap>,
@ -74,6 +75,7 @@ where
monero_wallet: Arc<monero::Wallet>,
db: Arc<Database>,
latest_rate: LR,
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
) -> Result<(Self, mpsc::Receiver<Swap>)> {
let swap_channel = MpscChannels::default();
@ -86,6 +88,7 @@ where
db,
latest_rate,
swap_sender: swap_channel.sender,
min_buy,
max_buy,
recv_encrypted_signature: Default::default(),
inflight_encrypted_signatures: Default::default(),
@ -206,7 +209,9 @@ where
}
SwarmEvent::Behaviour(OutEvent::SwapRequestDeclined { peer, error }) => {
match error {
Error::ResumeOnlyMode | Error::MaxBuyAmountExceeded { .. } => {
Error::ResumeOnlyMode
| Error::AmountBelowMinimum { .. }
| Error::AmountAboveMaximum { .. } => {
tracing::warn!(%peer, "Ignoring spot price request because: {}", error);
}
Error::BalanceTooLow { .. }
@ -228,7 +233,7 @@ where
}
}
let quote = match self.make_quote(self.max_buy).await {
let quote = match self.make_quote(self.min_buy, self.max_buy).await {
Ok(quote) => quote,
Err(e) => {
tracing::warn!(%peer, "Failed to make quote: {:#}", e);
@ -355,7 +360,11 @@ where
}
}
async fn make_quote(&mut self, max_buy: bitcoin::Amount) -> Result<BidQuote> {
async fn make_quote(
&mut self,
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
) -> Result<BidQuote> {
let rate = self
.latest_rate
.latest_rate()
@ -363,6 +372,7 @@ where
Ok(BidQuote {
price: rate.ask().context("Failed to compute asking price")?,
min_quantity: min_buy,
max_quantity: max_buy,
})
}

View file

@ -44,6 +44,8 @@ where
#[behaviour(ignore)]
lock_fee: monero::Amount,
#[behaviour(ignore)]
min_buy: bitcoin::Amount,
#[behaviour(ignore)]
max_buy: bitcoin::Amount,
#[behaviour(ignore)]
latest_rate: LR,
@ -62,6 +64,7 @@ where
pub fn new(
balance: monero::Amount,
lock_fee: monero::Amount,
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
latest_rate: LR,
resume_only: bool,
@ -75,6 +78,7 @@ where
events: Default::default(),
balance,
lock_fee,
min_buy,
max_buy,
latest_rate,
resume_only,
@ -156,8 +160,17 @@ where
}
let btc = request.btc;
if btc < self.min_buy {
self.decline(peer, channel, Error::AmountBelowMinimum {
min: self.min_buy,
buy: btc,
});
return;
}
if btc > self.max_buy {
self.decline(peer, channel, Error::MaxBuyAmountExceeded {
self.decline(peer, channel, Error::AmountAboveMaximum {
max: self.max_buy,
buy: btc,
});
@ -218,8 +231,13 @@ impl From<OutEvent> for alice::OutEvent {
pub enum Error {
#[error("ASB is running in resume-only mode")]
ResumeOnlyMode,
#[error("Maximum buy {max} exceeded {buy}")]
MaxBuyAmountExceeded {
#[error("Amount {buy} below minimum {min}")]
AmountBelowMinimum {
min: bitcoin::Amount,
buy: bitcoin::Amount,
},
#[error("Amount {buy} above maximum {max}")]
AmountAboveMaximum {
max: bitcoin::Amount,
buy: bitcoin::Amount,
},
@ -240,7 +258,11 @@ impl Error {
pub fn to_error_response(&self) -> spot_price::Error {
match self {
Error::ResumeOnlyMode => spot_price::Error::NoSwapsAccepted,
Error::MaxBuyAmountExceeded { max, buy } => spot_price::Error::MaxBuyAmountExceeded {
Error::AmountBelowMinimum { min, buy } => spot_price::Error::AmountBelowMinimum {
min: *min,
buy: *buy,
},
Error::AmountAboveMaximum { max, buy } => spot_price::Error::AmountAboveMaximum {
max: *max,
buy: *buy,
},
@ -268,6 +290,7 @@ mod tests {
Self {
balance: monero::Amount::from_monero(1.0).unwrap(),
lock_fee: monero::Amount::ZERO,
min_buy: bitcoin::Amount::from_btc(0.001).unwrap(),
max_buy: bitcoin::Amount::from_btc(0.01).unwrap(),
rate: TestRate::default(), // 0.01
resume_only: false,
@ -368,7 +391,32 @@ mod tests {
}
#[tokio::test]
async fn given_max_buy_exceeded_then_returns_error() {
async fn given_below_min_buy_then_returns_error() {
let min_buy = bitcoin::Amount::from_btc(0.001).unwrap();
let mut test =
SpotPriceTest::setup(AliceBehaviourValues::default().with_min_buy(min_buy)).await;
let btc_to_swap = bitcoin::Amount::from_btc(0.0001).unwrap();
let request = spot_price::Request { btc: btc_to_swap };
test.send_request(request);
test.assert_error(
alice::spot_price::Error::AmountBelowMinimum {
buy: btc_to_swap,
min: min_buy,
},
bob::spot_price::Error::AmountBelowMinimum {
buy: btc_to_swap,
min: min_buy,
},
)
.await;
}
#[tokio::test]
async fn given_above_max_buy_then_returns_error() {
let max_buy = bitcoin::Amount::from_btc(0.001).unwrap();
let mut test =
@ -380,11 +428,11 @@ mod tests {
test.send_request(request);
test.assert_error(
alice::spot_price::Error::MaxBuyAmountExceeded {
alice::spot_price::Error::AmountAboveMaximum {
buy: btc_to_swap,
max: max_buy,
},
bob::spot_price::Error::MaxBuyAmountExceeded {
bob::spot_price::Error::AmountAboveMaximum {
buy: btc_to_swap,
max: max_buy,
},
@ -461,6 +509,7 @@ mod tests {
Behaviour::new(
values.balance,
values.lock_fee,
values.min_buy,
values.max_buy,
values.rate.clone(),
values.resume_only,
@ -540,8 +589,12 @@ mod tests {
assert_eq!(buy1, buy2);
}
(
alice::spot_price::Error::MaxBuyAmountExceeded { .. },
alice::spot_price::Error::MaxBuyAmountExceeded { .. },
alice::spot_price::Error::AmountBelowMinimum { .. },
alice::spot_price::Error::AmountBelowMinimum { .. },
)
| (
alice::spot_price::Error::AmountAboveMaximum { .. },
alice::spot_price::Error::AmountAboveMaximum { .. },
)
| (
alice::spot_price::Error::LatestRateFetchFailed(_),
@ -583,6 +636,7 @@ mod tests {
struct AliceBehaviourValues {
pub balance: monero::Amount,
pub lock_fee: monero::Amount,
pub min_buy: bitcoin::Amount,
pub max_buy: bitcoin::Amount,
pub rate: TestRate, // 0.01
pub resume_only: bool,
@ -599,6 +653,11 @@ mod tests {
self
}
pub fn with_min_buy(mut self, min_buy: bitcoin::Amount) -> AliceBehaviourValues {
self.min_buy = min_buy;
self
}
pub fn with_max_buy(mut self, max_buy: bitcoin::Amount) -> AliceBehaviourValues {
self.max_buy = max_buy;
self

View file

@ -41,8 +41,13 @@ crate::impl_from_rr_event!(SpotPriceOutEvent, OutEvent, PROTOCOL);
pub enum Error {
#[error("Seller currently does not accept incoming swap requests, please try again later")]
NoSwapsAccepted,
#[error("Seller refused to buy {buy} because the minimum configured buy limit is {min}")]
AmountBelowMinimum {
min: bitcoin::Amount,
buy: bitcoin::Amount,
},
#[error("Seller refused to buy {buy} because the maximum configured buy limit is {max}")]
MaxBuyAmountExceeded {
AmountAboveMaximum {
max: bitcoin::Amount,
buy: bitcoin::Amount,
},
@ -59,8 +64,11 @@ impl From<spot_price::Error> for Error {
fn from(error: spot_price::Error) -> Self {
match error {
spot_price::Error::NoSwapsAccepted => Error::NoSwapsAccepted,
spot_price::Error::MaxBuyAmountExceeded { max, buy } => {
Error::MaxBuyAmountExceeded { max, buy }
spot_price::Error::AmountBelowMinimum { min, buy } => {
Error::AmountBelowMinimum { min, buy }
}
spot_price::Error::AmountAboveMaximum { max, buy } => {
Error::AmountAboveMaximum { max, buy }
}
spot_price::Error::BalanceTooLow { buy } => Error::BalanceTooLow { buy },
spot_price::Error::Other => Error::Other,

View file

@ -226,6 +226,7 @@ async fn start_alice(
let current_balance = monero_wallet.get_balance().await.unwrap();
let lock_fee = monero_wallet.static_tx_fee_estimate();
let min_buy = bitcoin::Amount::from_sat(u64::MIN);
let max_buy = bitcoin::Amount::from_sat(u64::MAX);
let latest_rate = FixedRate::default();
let resume_only = false;
@ -234,6 +235,7 @@ async fn start_alice(
&seed,
current_balance,
lock_fee,
min_buy,
max_buy,
latest_rate,
resume_only,
@ -248,7 +250,8 @@ async fn start_alice(
monero_wallet,
db,
FixedRate::default(),
bitcoin::Amount::ONE_BTC,
min_buy,
max_buy,
)
.unwrap();