mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-06 05:24:42 -04:00
Merge branch 'master' into libp2p_upgrade
This commit is contained in:
commit
ec169916d6
17 changed files with 307 additions and 161 deletions
|
@ -326,11 +326,13 @@ where
|
|||
.ask()
|
||||
.context("Failed to compute asking price")?;
|
||||
|
||||
let max_bitcoin_for_monero = self
|
||||
.monero_wallet
|
||||
.get_balance()
|
||||
.await?
|
||||
.max_bitcoin_for_price(ask_price);
|
||||
let xmr = self.monero_wallet.get_balance().await?;
|
||||
|
||||
let max_bitcoin_for_monero = xmr.max_bitcoin_for_price(ask_price).ok_or_else(|| {
|
||||
anyhow::anyhow!("Bitcoin price ({}) x Monero ({}) overflow", ask_price, xmr)
|
||||
})?;
|
||||
|
||||
tracing::debug!(%ask_price, %xmr, %max_bitcoin_for_monero);
|
||||
|
||||
if min_buy > max_bitcoin_for_monero {
|
||||
tracing::warn!(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
use tracing_subscriber::fmt::time::ChronoLocal;
|
||||
use tracing_subscriber::fmt::time::UtcTime;
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
|
||||
pub fn init(level: LevelFilter, json_format: bool, timestamp: bool) -> Result<()> {
|
||||
|
@ -14,7 +14,7 @@ pub fn init(level: LevelFilter, json_format: bool, timestamp: bool) -> Result<()
|
|||
.with_env_filter(format!("asb={},swap={}", level, level))
|
||||
.with_writer(std::io::stderr)
|
||||
.with_ansi(is_terminal)
|
||||
.with_timer(ChronoLocal::with_format("%F %T".to_owned()))
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.with_target(false);
|
||||
|
||||
match (json_format, timestamp) {
|
||||
|
|
|
@ -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<String> {
|
|||
Ok(qr_code)
|
||||
}
|
||||
|
||||
async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS>(
|
||||
async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS, FFE, TFE>(
|
||||
json: bool,
|
||||
bid_quote: impl Future<Output = Result<BidQuote>>,
|
||||
get_new_address: impl Future<Output = Result<bitcoin::Address>>,
|
||||
balance: FB,
|
||||
max_giveable_fn: FMG,
|
||||
sync: FS,
|
||||
estimate_fee: FFE,
|
||||
) -> Result<(bitcoin::Amount, bitcoin::Amount)>
|
||||
where
|
||||
TB: Future<Output = Result<bitcoin::Amount>>,
|
||||
|
@ -573,6 +577,8 @@ where
|
|||
FMG: Fn() -> TMG,
|
||||
TS: Future<Output = Result<()>>,
|
||||
FS: Fn() -> TS,
|
||||
FFE: Fn(bitcoin::Amount) -> TFE,
|
||||
TFE: Future<Output = Result<bitcoin::Amount>>,
|
||||
{
|
||||
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()
|
||||
|
|
|
@ -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<TxLock> for PartiallySignedTransaction {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use anyhow::Result;
|
||||
use std::option::Option::Some;
|
||||
use std::path::Path;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use tracing::subscriber::set_global_default;
|
||||
use tracing::{Event, Level, Subscriber};
|
||||
use tracing_subscriber::fmt::format::{DefaultFields, Format, JsonFields};
|
||||
use tracing_subscriber::fmt::time::ChronoLocal;
|
||||
use tracing_subscriber::fmt::time::UtcTime;
|
||||
use tracing_subscriber::layer::{Context, SubscriberExt};
|
||||
use tracing_subscriber::{fmt, EnvFilter, FmtSubscriber, Layer, Registry};
|
||||
use uuid::Uuid;
|
||||
|
@ -15,7 +16,8 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid
|
|||
|
||||
let registry = Registry::default().with(level_filter);
|
||||
|
||||
let appender = tracing_appender::rolling::never(dir, format!("swap-{}.log", swap_id));
|
||||
let appender =
|
||||
tracing_appender::rolling::never(dir.as_ref(), format!("swap-{}.log", swap_id));
|
||||
let (appender, guard) = tracing_appender::non_blocking(appender);
|
||||
|
||||
std::mem::forget(guard);
|
||||
|
@ -37,8 +39,6 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid
|
|||
} else {
|
||||
set_global_default(file_logger.with(info_terminal_printer()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
let level = if debug { Level::DEBUG } else { Level::INFO };
|
||||
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||
|
@ -47,7 +47,7 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid
|
|||
.with_env_filter(format!("swap={}", level))
|
||||
.with_writer(std::io::stderr)
|
||||
.with_ansi(is_terminal)
|
||||
.with_timer(ChronoLocal::with_format("%F %T".to_owned()))
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.with_target(false);
|
||||
|
||||
if json {
|
||||
|
@ -55,9 +55,10 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid
|
|||
} else {
|
||||
builder.init();
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
tracing::info!("Logging initialized to {}", dir.as_ref().display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct StdErrPrinter<L> {
|
||||
|
@ -79,25 +80,25 @@ type StdErrJsonLayer<S, T> = tracing_subscriber::fmt::Layer<
|
|||
fn() -> std::io::Stderr,
|
||||
>;
|
||||
|
||||
fn debug_terminal_printer<S>() -> StdErrPrinter<StdErrLayer<S, ChronoLocal>> {
|
||||
fn debug_terminal_printer<S>() -> StdErrPrinter<StdErrLayer<S, UtcTime<Rfc3339>>> {
|
||||
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||
StdErrPrinter {
|
||||
inner: fmt::layer()
|
||||
.with_ansi(is_terminal)
|
||||
.with_target(false)
|
||||
.with_timer(ChronoLocal::with_format("%F %T".to_owned()))
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.with_writer(std::io::stderr),
|
||||
level: Level::DEBUG,
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_json_terminal_printer<S>() -> StdErrPrinter<StdErrJsonLayer<S, ChronoLocal>> {
|
||||
fn debug_json_terminal_printer<S>() -> StdErrPrinter<StdErrJsonLayer<S, UtcTime<Rfc3339>>> {
|
||||
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||
StdErrPrinter {
|
||||
inner: fmt::layer()
|
||||
.with_ansi(is_terminal)
|
||||
.with_target(false)
|
||||
.with_timer(ChronoLocal::with_format("%F %T".to_owned()))
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.json()
|
||||
.with_writer(std::io::stderr),
|
||||
level: Level::DEBUG,
|
||||
|
|
|
@ -81,8 +81,8 @@ pub struct PublicViewKey(PublicKey);
|
|||
#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, PartialOrd)]
|
||||
pub struct Amount(u64);
|
||||
|
||||
// Median tx fees on Monero as found here: https://www.monero.how/monero-transaction-fees, XMR 0.000_015 * 2 (to be on the safe side)
|
||||
pub const MONERO_FEE: Amount = Amount::from_piconero(30000000);
|
||||
// Median tx fees on Monero as found here: https://www.monero.how/monero-transaction-fees, XMR 0.000_008 * 2 (to be on the safe side)
|
||||
pub const MONERO_FEE: Amount = Amount::from_piconero(16_000_000);
|
||||
|
||||
impl Amount {
|
||||
pub const ZERO: Self = Self(0);
|
||||
|
@ -95,22 +95,30 @@ impl Amount {
|
|||
Amount(amount)
|
||||
}
|
||||
|
||||
/// Return Monero Amount as Piconero.
|
||||
pub fn as_piconero(&self) -> u64 {
|
||||
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());
|
||||
/// Calculate the maximum amount of Bitcoin that can be bought at a given
|
||||
/// asking price for this amount of Monero including the median fee.
|
||||
pub fn max_bitcoin_for_price(&self, ask_price: bitcoin::Amount) -> Option<bitcoin::Amount> {
|
||||
let pico_minus_fee = self.as_piconero().saturating_sub(MONERO_FEE.as_piconero());
|
||||
|
||||
if piconero_minus_fee == 0 {
|
||||
return bitcoin::Amount::ZERO;
|
||||
if pico_minus_fee == 0 {
|
||||
return Some(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;
|
||||
// safely convert the BTC/XMR rate to sat/pico
|
||||
let ask_sats = Decimal::from(ask_price.as_sat());
|
||||
let pico_per_xmr = Decimal::from(PICONERO_OFFSET);
|
||||
let ask_sats_per_pico = ask_sats / pico_per_xmr;
|
||||
|
||||
bitcoin::Amount::from_sat(piconero_calc)
|
||||
let pico = Decimal::from(pico_minus_fee);
|
||||
let max_sats = pico.checked_mul(ask_sats_per_pico)?;
|
||||
let satoshi = max_sats.to_u64()?;
|
||||
|
||||
Some(bitcoin::Amount::from_sat(satoshi))
|
||||
}
|
||||
|
||||
pub fn from_monero(amount: f64) -> Result<Self> {
|
||||
|
@ -375,27 +383,88 @@ 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);
|
||||
fn max_bitcoin_to_trade() {
|
||||
// sanity check: if the asking price is 1 BTC / 1 XMR
|
||||
// and we have μ XMR + fee
|
||||
// then max BTC we can buy is μ
|
||||
let ask = bitcoin::Amount::from_btc(1.0).unwrap();
|
||||
|
||||
let monero_max_from_bitcoin = amount.max_bitcoin_for_price(bitcoin_price_sats);
|
||||
let xmr = Amount::parse_monero("1.0").unwrap() + MONERO_FEE;
|
||||
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
bitcoin::Amount::from_sat(3_828_988),
|
||||
monero_max_from_bitcoin
|
||||
);
|
||||
assert_eq!(btc, bitcoin::Amount::from_btc(1.0).unwrap());
|
||||
|
||||
let xmr = Amount::parse_monero("0.5").unwrap() + MONERO_FEE;
|
||||
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
|
||||
|
||||
assert_eq!(btc, bitcoin::Amount::from_btc(0.5).unwrap());
|
||||
|
||||
let xmr = Amount::parse_monero("2.5").unwrap() + MONERO_FEE;
|
||||
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
|
||||
|
||||
assert_eq!(btc, bitcoin::Amount::from_btc(2.5).unwrap());
|
||||
|
||||
let xmr = Amount::parse_monero("420").unwrap() + MONERO_FEE;
|
||||
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
|
||||
|
||||
assert_eq!(btc, bitcoin::Amount::from_btc(420.0).unwrap());
|
||||
|
||||
let xmr = Amount::parse_monero("0.00001").unwrap() + MONERO_FEE;
|
||||
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
|
||||
|
||||
assert_eq!(btc, bitcoin::Amount::from_btc(0.00001).unwrap());
|
||||
|
||||
// other ask prices
|
||||
|
||||
let ask = bitcoin::Amount::from_btc(0.5).unwrap();
|
||||
let xmr = Amount::parse_monero("2").unwrap() + MONERO_FEE;
|
||||
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
|
||||
|
||||
assert_eq!(btc, bitcoin::Amount::from_btc(1.0).unwrap());
|
||||
|
||||
let ask = bitcoin::Amount::from_btc(2.0).unwrap();
|
||||
let xmr = Amount::parse_monero("1").unwrap() + MONERO_FEE;
|
||||
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
|
||||
|
||||
assert_eq!(btc, bitcoin::Amount::from_btc(2.0).unwrap());
|
||||
|
||||
let ask = bitcoin::Amount::from_sat(382_900);
|
||||
let xmr = Amount::parse_monero("10").unwrap();
|
||||
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
|
||||
|
||||
assert_eq!(btc, bitcoin::Amount::from_sat(3_828_993));
|
||||
|
||||
// example from https://github.com/comit-network/xmr-btc-swap/issues/1084
|
||||
// with rate from kraken at that time
|
||||
let ask = bitcoin::Amount::from_sat(685_800);
|
||||
let xmr = Amount::parse_monero("0.826286435921").unwrap();
|
||||
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
|
||||
|
||||
assert_eq!(btc, bitcoin::Amount::from_sat(566_656));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_bitcoin_to_trade_overflow() {
|
||||
let xmr = Amount::from_monero(30.0).unwrap();
|
||||
let ask = bitcoin::Amount::from_sat(728_688);
|
||||
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
|
||||
|
||||
assert_eq!(bitcoin::Amount::from_sat(21_860_628), btc);
|
||||
|
||||
let xmr = Amount::from_piconero(u64::MAX);
|
||||
let ask = bitcoin::Amount::from_sat(u64::MAX);
|
||||
let btc = xmr.max_bitcoin_for_price(ask);
|
||||
|
||||
assert!(btc.is_none());
|
||||
}
|
||||
|
||||
#[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 ask = bitcoin::Amount::from_sat(382_900);
|
||||
let xmr = Amount::parse_monero("0.00001").unwrap();
|
||||
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
|
||||
|
||||
let monero_max_from_bitcoin = amount.max_bitcoin_for_price(bitcoin_price_sats);
|
||||
|
||||
assert_eq!(bitcoin::Amount::ZERO, monero_max_from_bitcoin);
|
||||
assert_eq!(bitcoin::Amount::ZERO, btc);
|
||||
}
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
|
|
|
@ -314,8 +314,13 @@ async fn wait_for_confirmations<C: monero_rpc::wallet::MoneroWalletRpc<reqwest::
|
|||
.await
|
||||
{
|
||||
Ok(proof) => proof,
|
||||
Err(jsonrpc::Error::JsonRpc(jsonrpc::JsonRpcError { code: -1, .. })) => {
|
||||
tracing::warn!(%txid, "`monero-wallet-rpc` failed to fetch transaction, may need to be restarted");
|
||||
Err(jsonrpc::Error::JsonRpc(jsonrpc::JsonRpcError {
|
||||
code: -1,
|
||||
message,
|
||||
data,
|
||||
})) => {
|
||||
tracing::debug!(message, ?data);
|
||||
tracing::warn!(%txid, message, "`monero-wallet-rpc` failed to fetch transaction, may need to be restarted");
|
||||
continue;
|
||||
}
|
||||
// TODO: Implement this using a generic proxy for each function call once https://github.com/thomaseizinger/rust-jsonrpc-client/issues/47 is fixed.
|
||||
|
|
|
@ -41,7 +41,7 @@ impl MakeCapturingWriter {
|
|||
}
|
||||
}
|
||||
|
||||
impl MakeWriter for MakeCapturingWriter {
|
||||
impl<'a> MakeWriter<'a> for MakeCapturingWriter {
|
||||
type Writer = CapturingWriter;
|
||||
|
||||
fn make_writer(&self) -> Self::Writer {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue