mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Merge #61
61: Prepare CLI for mainnet swap r=da-kami a=da-kami Co-authored-by: Daniel Karzel <daniel@comit.network>
This commit is contained in:
commit
9fecf7008e
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -2888,6 +2888,16 @@ dependencies = [
|
|||||||
"crossbeam-utils 0.7.2",
|
"crossbeam-utils 0.7.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust_decimal"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c9e81662973c7a8d9663e64a0de4cd642b89a21d64966e3d99606efdc5fb0cc6"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hex"
|
name = "rustc-hex"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@ -4182,6 +4192,7 @@ dependencies = [
|
|||||||
"monero-harness",
|
"monero-harness",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"rust_decimal",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_cbor",
|
"serde_cbor",
|
||||||
"sha2 0.9.2",
|
"sha2 0.9.2",
|
||||||
|
@ -13,8 +13,226 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prettytable::{row, Table};
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
use swap::{
|
||||||
|
alice, alice::swap::AliceState, bitcoin, bob, bob::swap::BobState, cli::Options, monero,
|
||||||
|
network::transport::build, recover::recover, storage::Database, trace::init_tracing,
|
||||||
|
SwapAmounts,
|
||||||
|
};
|
||||||
|
use tracing::{info, log::LevelFilter};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use xmr_btc::{alice::State0, config::Config, cross_curve_dleq};
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate prettytable;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
unimplemented!()
|
init_tracing(LevelFilter::Trace).expect("initialize tracing");
|
||||||
|
|
||||||
|
let opt = Options::from_args();
|
||||||
|
|
||||||
|
// This currently creates the directory if it's not there in the first place
|
||||||
|
let db = Database::open(std::path::Path::new("./.swap-db/")).unwrap();
|
||||||
|
let config = Config::mainnet();
|
||||||
|
|
||||||
|
match opt {
|
||||||
|
Options::SellXmr {
|
||||||
|
bitcoind_url,
|
||||||
|
bitcoin_wallet_name,
|
||||||
|
monero_wallet_rpc_url,
|
||||||
|
listen_addr,
|
||||||
|
send_monero,
|
||||||
|
receive_bitcoin,
|
||||||
|
} => {
|
||||||
|
info!("running swap node as Alice ...");
|
||||||
|
|
||||||
|
let bitcoin_wallet = bitcoin::Wallet::new(
|
||||||
|
bitcoin_wallet_name.as_str(),
|
||||||
|
bitcoind_url,
|
||||||
|
config.bitcoin_network,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("failed to create bitcoin wallet");
|
||||||
|
|
||||||
|
let bitcoin_balance = bitcoin_wallet.balance().await?;
|
||||||
|
info!(
|
||||||
|
"Connection to Bitcoin wallet succeeded, balance: {}",
|
||||||
|
bitcoin_balance
|
||||||
|
);
|
||||||
|
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
||||||
|
|
||||||
|
let monero_wallet = monero::Wallet::new(monero_wallet_rpc_url);
|
||||||
|
let monero_balance = monero_wallet.get_balance().await?;
|
||||||
|
info!(
|
||||||
|
"Connection to Monero wallet succeeded, balance: {}",
|
||||||
|
monero_balance
|
||||||
|
);
|
||||||
|
let monero_wallet = Arc::new(monero_wallet);
|
||||||
|
|
||||||
|
let amounts = SwapAmounts {
|
||||||
|
btc: receive_bitcoin,
|
||||||
|
xmr: send_monero,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (alice_state, alice_behaviour) = {
|
||||||
|
let rng = &mut OsRng;
|
||||||
|
let a = bitcoin::SecretKey::new_random(rng);
|
||||||
|
let s_a = cross_curve_dleq::Scalar::random(rng);
|
||||||
|
let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng);
|
||||||
|
let redeem_address = bitcoin_wallet.as_ref().new_address().await.unwrap();
|
||||||
|
let punish_address = redeem_address.clone();
|
||||||
|
let state0 = State0::new(
|
||||||
|
a,
|
||||||
|
s_a,
|
||||||
|
v_a,
|
||||||
|
amounts.btc,
|
||||||
|
amounts.xmr,
|
||||||
|
config.bitcoin_refund_timelock,
|
||||||
|
config.bitcoin_punish_timelock,
|
||||||
|
redeem_address,
|
||||||
|
punish_address,
|
||||||
|
);
|
||||||
|
|
||||||
|
(
|
||||||
|
AliceState::Started {
|
||||||
|
amounts,
|
||||||
|
state0: state0.clone(),
|
||||||
|
},
|
||||||
|
alice::Behaviour::new(state0),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let alice_peer_id = alice_behaviour.peer_id();
|
||||||
|
info!(
|
||||||
|
"Alice Peer ID (to be used by Bob to dial her): {}",
|
||||||
|
alice_peer_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let alice_transport = build(alice_behaviour.identity())?;
|
||||||
|
|
||||||
|
let (mut event_loop, handle) =
|
||||||
|
alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?;
|
||||||
|
|
||||||
|
let swap = alice::swap::swap(
|
||||||
|
alice_state,
|
||||||
|
handle,
|
||||||
|
bitcoin_wallet.clone(),
|
||||||
|
monero_wallet.clone(),
|
||||||
|
config,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _event_loop = tokio::spawn(async move { event_loop.run().await });
|
||||||
|
swap.await?;
|
||||||
|
}
|
||||||
|
Options::BuyXmr {
|
||||||
|
alice_addr,
|
||||||
|
alice_peer_id,
|
||||||
|
bitcoind_url,
|
||||||
|
bitcoin_wallet_name,
|
||||||
|
monero_wallet_rpc_url,
|
||||||
|
send_bitcoin,
|
||||||
|
receive_monero,
|
||||||
|
} => {
|
||||||
|
info!("running swap node as Bob ...");
|
||||||
|
|
||||||
|
let bob_behaviour = bob::Behaviour::default();
|
||||||
|
let bob_transport = build(bob_behaviour.identity())?;
|
||||||
|
|
||||||
|
let bitcoin_wallet = bitcoin::Wallet::new(
|
||||||
|
bitcoin_wallet_name.as_str(),
|
||||||
|
bitcoind_url,
|
||||||
|
config.bitcoin_network,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("failed to create bitcoin wallet");
|
||||||
|
let bitcoin_balance = bitcoin_wallet.balance().await?;
|
||||||
|
info!(
|
||||||
|
"Connection to Bitcoin wallet succeeded, balance: {}",
|
||||||
|
bitcoin_balance
|
||||||
|
);
|
||||||
|
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
||||||
|
|
||||||
|
let monero_wallet = monero::Wallet::new(monero_wallet_rpc_url);
|
||||||
|
let monero_balance = monero_wallet.get_balance().await?;
|
||||||
|
info!(
|
||||||
|
"Connection to Monero wallet succeeded, balance: {}",
|
||||||
|
monero_balance
|
||||||
|
);
|
||||||
|
let monero_wallet = Arc::new(monero_wallet);
|
||||||
|
|
||||||
|
let refund_address = bitcoin_wallet.new_address().await.unwrap();
|
||||||
|
let state0 = xmr_btc::bob::State0::new(
|
||||||
|
&mut OsRng,
|
||||||
|
send_bitcoin,
|
||||||
|
receive_monero,
|
||||||
|
config.bitcoin_refund_timelock,
|
||||||
|
config.bitcoin_punish_timelock,
|
||||||
|
refund_address,
|
||||||
|
);
|
||||||
|
|
||||||
|
let amounts = SwapAmounts {
|
||||||
|
btc: send_bitcoin,
|
||||||
|
xmr: receive_monero,
|
||||||
|
};
|
||||||
|
|
||||||
|
let bob_state = BobState::Started {
|
||||||
|
state0,
|
||||||
|
amounts,
|
||||||
|
peer_id: alice_peer_id,
|
||||||
|
addr: alice_addr,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (event_loop, handle) =
|
||||||
|
bob::event_loop::EventLoop::new(bob_transport, bob_behaviour).unwrap();
|
||||||
|
|
||||||
|
let swap = bob::swap::swap(
|
||||||
|
bob_state,
|
||||||
|
handle,
|
||||||
|
db,
|
||||||
|
bitcoin_wallet.clone(),
|
||||||
|
monero_wallet.clone(),
|
||||||
|
OsRng,
|
||||||
|
Uuid::new_v4(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let _event_loop = tokio::spawn(async move { event_loop.run().await });
|
||||||
|
swap.await?;
|
||||||
|
}
|
||||||
|
Options::History => {
|
||||||
|
let mut table = Table::new();
|
||||||
|
|
||||||
|
table.add_row(row!["SWAP ID", "STATE"]);
|
||||||
|
|
||||||
|
for (swap_id, state) in db.all()? {
|
||||||
|
table.add_row(row![swap_id, state]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the table to stdout
|
||||||
|
table.printstd();
|
||||||
|
}
|
||||||
|
Options::Recover {
|
||||||
|
swap_id,
|
||||||
|
bitcoind_url,
|
||||||
|
monerod_url,
|
||||||
|
bitcoin_wallet_name,
|
||||||
|
} => {
|
||||||
|
let state = db.get_state(swap_id)?;
|
||||||
|
let bitcoin_wallet = bitcoin::Wallet::new(
|
||||||
|
bitcoin_wallet_name.as_ref(),
|
||||||
|
bitcoind_url,
|
||||||
|
config.bitcoin_network,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("failed to create bitcoin wallet");
|
||||||
|
let monero_wallet = monero::Wallet::new(monerod_url);
|
||||||
|
|
||||||
|
recover(bitcoin_wallet, monero_wallet, state).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -20,27 +20,33 @@ pub use xmr_btc::bitcoin::*;
|
|||||||
pub const TX_LOCK_MINE_TIMEOUT: u64 = 3600;
|
pub const TX_LOCK_MINE_TIMEOUT: u64 = 3600;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Wallet(pub bitcoin_harness::Wallet);
|
pub struct Wallet {
|
||||||
|
pub inner: bitcoin_harness::Wallet,
|
||||||
|
pub network: bitcoin::Network,
|
||||||
|
}
|
||||||
|
|
||||||
impl Wallet {
|
impl Wallet {
|
||||||
pub async fn new(name: &str, url: Url) -> Result<Self> {
|
pub async fn new(name: &str, url: Url, network: bitcoin::Network) -> Result<Self> {
|
||||||
let wallet = bitcoin_harness::Wallet::new(name, url).await?;
|
let wallet = bitcoin_harness::Wallet::new(name, url).await?;
|
||||||
|
|
||||||
Ok(Self(wallet))
|
Ok(Self {
|
||||||
|
inner: wallet,
|
||||||
|
network,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn balance(&self) -> Result<Amount> {
|
pub async fn balance(&self) -> Result<Amount> {
|
||||||
let balance = self.0.balance().await?;
|
let balance = self.inner.balance().await?;
|
||||||
Ok(balance)
|
Ok(balance)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new_address(&self) -> Result<Address> {
|
pub async fn new_address(&self) -> Result<Address> {
|
||||||
self.0.new_address().await.map_err(Into::into)
|
self.inner.new_address().await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn transaction_fee(&self, txid: Txid) -> Result<Amount> {
|
pub async fn transaction_fee(&self, txid: Txid) -> Result<Amount> {
|
||||||
let fee = self
|
let fee = self
|
||||||
.0
|
.inner
|
||||||
.get_wallet_transaction(txid)
|
.get_wallet_transaction(txid)
|
||||||
.await
|
.await
|
||||||
.map(|res| {
|
.map(|res| {
|
||||||
@ -64,7 +70,7 @@ impl BuildTxLockPsbt for Wallet {
|
|||||||
output_address: Address,
|
output_address: Address,
|
||||||
output_amount: Amount,
|
output_amount: Amount,
|
||||||
) -> Result<PartiallySignedTransaction> {
|
) -> Result<PartiallySignedTransaction> {
|
||||||
let psbt = self.0.fund_psbt(output_address, output_amount).await?;
|
let psbt = self.inner.fund_psbt(output_address, output_amount).await?;
|
||||||
let as_hex = base64::decode(psbt)?;
|
let as_hex = base64::decode(psbt)?;
|
||||||
|
|
||||||
let psbt = bitcoin::consensus::deserialize(&as_hex)?;
|
let psbt = bitcoin::consensus::deserialize(&as_hex)?;
|
||||||
@ -81,7 +87,10 @@ impl SignTxLock for Wallet {
|
|||||||
let psbt = bitcoin::consensus::serialize(&psbt);
|
let psbt = bitcoin::consensus::serialize(&psbt);
|
||||||
let as_base64 = base64::encode(psbt);
|
let as_base64 = base64::encode(psbt);
|
||||||
|
|
||||||
let psbt = self.0.wallet_process_psbt(PsbtBase64(as_base64)).await?;
|
let psbt = self
|
||||||
|
.inner
|
||||||
|
.wallet_process_psbt(PsbtBase64(as_base64))
|
||||||
|
.await?;
|
||||||
let PsbtBase64(signed_psbt) = PsbtBase64::from(psbt);
|
let PsbtBase64(signed_psbt) = PsbtBase64::from(psbt);
|
||||||
|
|
||||||
let as_hex = base64::decode(signed_psbt)?;
|
let as_hex = base64::decode(signed_psbt)?;
|
||||||
@ -96,7 +105,9 @@ impl SignTxLock for Wallet {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl BroadcastSignedTransaction for Wallet {
|
impl BroadcastSignedTransaction for Wallet {
|
||||||
async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result<Txid> {
|
async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result<Txid> {
|
||||||
Ok(self.0.send_raw_transaction(transaction).await?)
|
let txid = self.inner.send_raw_transaction(transaction).await?;
|
||||||
|
tracing::debug!("Bitcoin tx broadcasted! TXID = {}", txid);
|
||||||
|
Ok(txid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +116,7 @@ impl BroadcastSignedTransaction for Wallet {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WatchForRawTransaction for Wallet {
|
impl WatchForRawTransaction for Wallet {
|
||||||
async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction {
|
async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction {
|
||||||
(|| async { Ok(self.0.get_raw_transaction(txid).await?) })
|
(|| async { Ok(self.inner.get_raw_transaction(txid).await?) })
|
||||||
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
||||||
.await
|
.await
|
||||||
.expect("transient errors to be retried")
|
.expect("transient errors to be retried")
|
||||||
@ -116,14 +127,14 @@ impl WatchForRawTransaction for Wallet {
|
|||||||
impl GetRawTransaction for Wallet {
|
impl GetRawTransaction for Wallet {
|
||||||
// todo: potentially replace with option
|
// todo: potentially replace with option
|
||||||
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
|
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
|
||||||
Ok(self.0.get_raw_transaction(txid).await?)
|
Ok(self.inner.get_raw_transaction(txid).await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl BlockHeight for Wallet {
|
impl BlockHeight for Wallet {
|
||||||
async fn block_height(&self) -> u32 {
|
async fn block_height(&self) -> u32 {
|
||||||
(|| async { Ok(self.0.client.getblockcount().await?) })
|
(|| async { Ok(self.inner.client.getblockcount().await?) })
|
||||||
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
||||||
.await
|
.await
|
||||||
.expect("transient errors to be retried")
|
.expect("transient errors to be retried")
|
||||||
@ -141,7 +152,7 @@ impl TransactionBlockHeight for Wallet {
|
|||||||
|
|
||||||
(|| async {
|
(|| async {
|
||||||
let block_height = self
|
let block_height = self
|
||||||
.0
|
.inner
|
||||||
.transaction_block_height(txid)
|
.transaction_block_height(txid)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| backoff::Error::Transient(Error::Io))?;
|
.map_err(|_| backoff::Error::Transient(Error::Io))?;
|
||||||
@ -167,7 +178,7 @@ impl WaitForTransactionFinality for Wallet {
|
|||||||
let mut interval = interval(config.bitcoin_avg_block_time / 4);
|
let mut interval = interval(config.bitcoin_avg_block_time / 4);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let tx = self.0.client.get_raw_transaction_verbose(txid).await?;
|
let tx = self.inner.client.get_raw_transaction_verbose(txid).await?;
|
||||||
if let Some(confirmations) = tx.confirmations {
|
if let Some(confirmations) = tx.confirmations {
|
||||||
if confirmations >= config.bitcoin_finality_confirmations {
|
if confirmations >= config.bitcoin_finality_confirmations {
|
||||||
break;
|
break;
|
||||||
@ -179,3 +190,9 @@ impl WaitForTransactionFinality for Wallet {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Network for Wallet {
|
||||||
|
fn get_network(&self) -> bitcoin::Network {
|
||||||
|
self.network
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,38 +1,70 @@
|
|||||||
use libp2p::core::Multiaddr;
|
use libp2p::{core::Multiaddr, PeerId};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(structopt::StructOpt, Debug)]
|
#[derive(structopt::StructOpt, Debug)]
|
||||||
#[structopt(name = "xmr-btc-swap", about = "Trustless XMR BTC swaps")]
|
#[structopt(name = "xmr-btc-swap", about = "Trustless XMR BTC swaps")]
|
||||||
pub enum Options {
|
pub enum Options {
|
||||||
Alice {
|
SellXmr {
|
||||||
#[structopt(default_value = "http://127.0.0.1:8332", long = "bitcoind")]
|
#[structopt(
|
||||||
|
short = "b",
|
||||||
|
long = "bitcoind",
|
||||||
|
default_value = "http://127.0.0.1:8332"
|
||||||
|
)]
|
||||||
bitcoind_url: Url,
|
bitcoind_url: Url,
|
||||||
|
|
||||||
#[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")]
|
#[structopt(short = "n", long = "bitcoin-wallet-name")]
|
||||||
monerod_url: Url,
|
bitcoin_wallet_name: String,
|
||||||
|
|
||||||
#[structopt(default_value = "/ip4/127.0.0.1/tcp/9876", long = "listen-addr")]
|
#[structopt(
|
||||||
|
short = "m",
|
||||||
|
long = "monero-wallet-rpc",
|
||||||
|
default_value = "http://127.0.0.1:18083/json_rpc"
|
||||||
|
)]
|
||||||
|
monero_wallet_rpc_url: Url,
|
||||||
|
|
||||||
|
#[structopt(
|
||||||
|
short = "a",
|
||||||
|
long = "listen-addr",
|
||||||
|
default_value = "/ip4/127.0.0.1/tcp/9876"
|
||||||
|
)]
|
||||||
listen_addr: Multiaddr,
|
listen_addr: Multiaddr,
|
||||||
|
|
||||||
#[structopt(long = "tor-port")]
|
#[structopt(short = "s", long = "send-xmr", help = "Monero amount as floating point nr without denomination (e.g. 125.1)", parse(try_from_str = parse_xmr))]
|
||||||
tor_port: Option<u16>,
|
send_monero: xmr_btc::monero::Amount,
|
||||||
},
|
|
||||||
Bob {
|
|
||||||
#[structopt(long = "sats")]
|
|
||||||
satoshis: u64,
|
|
||||||
|
|
||||||
#[structopt(long = "alice-addr")]
|
#[structopt(short = "r", long = "receive-btc", help = "Bitcoin amount as floating point nr without denomination (e.g. 1.25)", parse(try_from_str = parse_btc))]
|
||||||
|
receive_bitcoin: bitcoin::Amount,
|
||||||
|
},
|
||||||
|
BuyXmr {
|
||||||
|
#[structopt(short = "a", long = "connect-addr")]
|
||||||
alice_addr: Multiaddr,
|
alice_addr: Multiaddr,
|
||||||
|
|
||||||
#[structopt(default_value = "http://127.0.0.1:8332", long = "bitcoind")]
|
#[structopt(short = "p", long = "connect-peer-id")]
|
||||||
|
alice_peer_id: PeerId,
|
||||||
|
|
||||||
|
#[structopt(
|
||||||
|
short = "b",
|
||||||
|
long = "bitcoind",
|
||||||
|
default_value = "http://127.0.0.1:8332"
|
||||||
|
)]
|
||||||
bitcoind_url: Url,
|
bitcoind_url: Url,
|
||||||
|
|
||||||
#[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")]
|
#[structopt(short = "n", long = "bitcoin-wallet-name")]
|
||||||
monerod_url: Url,
|
bitcoin_wallet_name: String,
|
||||||
|
|
||||||
#[structopt(long = "tor")]
|
#[structopt(
|
||||||
tor: bool,
|
short = "m",
|
||||||
|
long = "monerod",
|
||||||
|
default_value = "http://127.0.0.1:18083/json_rpc"
|
||||||
|
)]
|
||||||
|
monero_wallet_rpc_url: Url,
|
||||||
|
|
||||||
|
#[structopt(short = "s", long = "send-btc", help = "Bitcoin amount as floating point nr without denomination (e.g. 1.25)", parse(try_from_str = parse_btc))]
|
||||||
|
send_bitcoin: bitcoin::Amount,
|
||||||
|
|
||||||
|
#[structopt(short = "r", long = "receive-xmr", help = "Monero amount as floating point nr without denomination (e.g. 125.1)", parse(try_from_str = parse_xmr))]
|
||||||
|
receive_monero: xmr_btc::monero::Amount,
|
||||||
},
|
},
|
||||||
History,
|
History,
|
||||||
Recover {
|
Recover {
|
||||||
@ -44,5 +76,18 @@ pub enum Options {
|
|||||||
|
|
||||||
#[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")]
|
#[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")]
|
||||||
monerod_url: Url,
|
monerod_url: Url,
|
||||||
|
|
||||||
|
#[structopt(short = "n", long = "bitcoin-wallet-name")]
|
||||||
|
bitcoin_wallet_name: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_btc(str: &str) -> anyhow::Result<bitcoin::Amount> {
|
||||||
|
let amount = bitcoin::Amount::from_str_in(str, ::bitcoin::Denomination::Bitcoin)?;
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_xmr(str: &str) -> anyhow::Result<xmr_btc::monero::Amount> {
|
||||||
|
let amount = xmr_btc::monero::Amount::parse_monero(str)?;
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ pub mod recover;
|
|||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
pub mod tor;
|
pub mod tor;
|
||||||
|
pub mod trace;
|
||||||
|
|
||||||
pub type Never = std::convert::Infallible;
|
pub type Never = std::convert::Infallible;
|
||||||
|
|
||||||
|
@ -39,11 +39,15 @@ impl Transfer for Wallet {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let tx_hash = TxHash(res.tx_hash);
|
let tx_hash = TxHash(res.tx_hash);
|
||||||
|
tracing::debug!("Monero tx broadcasted!, tx hash: {:?}", tx_hash);
|
||||||
let tx_key = PrivateKey::from_str(&res.tx_key)?;
|
let tx_key = PrivateKey::from_str(&res.tx_key)?;
|
||||||
|
|
||||||
let fee = Amount::from_piconero(res.fee);
|
let fee = Amount::from_piconero(res.fee);
|
||||||
|
|
||||||
Ok((TransferProof::new(tx_hash, tx_key), fee))
|
let transfer_proof = TransferProof::new(tx_hash, tx_key);
|
||||||
|
tracing::debug!(" Transfer proof: {:?}", transfer_proof);
|
||||||
|
|
||||||
|
Ok((transfer_proof, fee))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ pub async fn alice_recover(
|
|||||||
|
|
||||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
info!("Checking if the Bitcoin cancel transaction has been published");
|
||||||
if bitcoin_wallet
|
if bitcoin_wallet
|
||||||
.0
|
.inner
|
||||||
.get_raw_transaction(tx_cancel.txid())
|
.get_raw_transaction(tx_cancel.txid())
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
@ -164,7 +164,7 @@ pub async fn alice_recover(
|
|||||||
.transaction_block_height(state.tx_lock.txid())
|
.transaction_block_height(state.tx_lock.txid())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let block_height = bitcoin_wallet.0.client.getblockcount().await?;
|
let block_height = bitcoin_wallet.inner.client.getblockcount().await?;
|
||||||
let refund_absolute_expiry = tx_lock_height + state.refund_timelock;
|
let refund_absolute_expiry = tx_lock_height + state.refund_timelock;
|
||||||
|
|
||||||
info!("Checking refund timelock");
|
info!("Checking refund timelock");
|
||||||
@ -187,7 +187,7 @@ pub async fn alice_recover(
|
|||||||
|
|
||||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
info!("Checking if the Bitcoin cancel transaction has been published");
|
||||||
if bitcoin_wallet
|
if bitcoin_wallet
|
||||||
.0
|
.inner
|
||||||
.get_raw_transaction(tx_cancel.txid())
|
.get_raw_transaction(tx_cancel.txid())
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
@ -290,7 +290,11 @@ pub async fn alice_recover(
|
|||||||
|
|
||||||
// TODO: Protect against transient errors so that we can correctly decide if the
|
// TODO: Protect against transient errors so that we can correctly decide if the
|
||||||
// bitcoin has been refunded
|
// bitcoin has been refunded
|
||||||
match bitcoin_wallet.0.get_raw_transaction(tx_refund.txid()).await {
|
match bitcoin_wallet
|
||||||
|
.inner
|
||||||
|
.get_raw_transaction(tx_refund.txid())
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(tx_refund_published) => {
|
Ok(tx_refund_published) => {
|
||||||
info!("Bitcoin already refunded");
|
info!("Bitcoin already refunded");
|
||||||
|
|
||||||
@ -375,7 +379,7 @@ pub async fn bob_recover(
|
|||||||
|
|
||||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
info!("Checking if the Bitcoin cancel transaction has been published");
|
||||||
if bitcoin_wallet
|
if bitcoin_wallet
|
||||||
.0
|
.inner
|
||||||
.get_raw_transaction(tx_cancel.txid())
|
.get_raw_transaction(tx_cancel.txid())
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
@ -431,7 +435,7 @@ pub async fn bob_recover(
|
|||||||
|
|
||||||
let tx_redeem = bitcoin::TxRedeem::new(&state.tx_lock, &state.redeem_address);
|
let tx_redeem = bitcoin::TxRedeem::new(&state.tx_lock, &state.redeem_address);
|
||||||
let tx_redeem_published = bitcoin_wallet
|
let tx_redeem_published = bitcoin_wallet
|
||||||
.0
|
.inner
|
||||||
.get_raw_transaction(tx_redeem.txid())
|
.get_raw_transaction(tx_redeem.txid())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use atty::{self, Stream};
|
use atty::{self};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use tracing::{info, subscriber};
|
use tracing::{info, subscriber};
|
||||||
use tracing_log::LogTracer;
|
use tracing_log::LogTracer;
|
||||||
@ -9,15 +9,16 @@ pub fn init_tracing(level: log::LevelFilter) -> anyhow::Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upstream log filter.
|
// We want upstream library log messages, just only at Info level.
|
||||||
LogTracer::init_with_filter(LevelFilter::Debug)?;
|
LogTracer::init_with_filter(LevelFilter::Info)?;
|
||||||
|
|
||||||
let is_terminal = atty::is(Stream::Stdout);
|
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||||
let subscriber = FmtSubscriber::builder()
|
let subscriber = FmtSubscriber::builder()
|
||||||
.with_env_filter(format!(
|
.with_env_filter(format!(
|
||||||
"swap={},xmr_btc={},monero_harness={}",
|
"swap={},xmr-btc={},http=warn,warp=warn",
|
||||||
level, level, level
|
level, level
|
||||||
))
|
))
|
||||||
|
.with_writer(std::io::stderr)
|
||||||
.with_ansi(is_terminal)
|
.with_ansi(is_terminal)
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
|
@ -241,7 +241,7 @@ async fn init_alice(
|
|||||||
));
|
));
|
||||||
|
|
||||||
let alice_btc_wallet = Arc::new(
|
let alice_btc_wallet = Arc::new(
|
||||||
swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone())
|
swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone(), config.bitcoin_network)
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
@ -316,13 +316,13 @@ async fn init_bob(
|
|||||||
Database,
|
Database,
|
||||||
) {
|
) {
|
||||||
let bob_btc_wallet = Arc::new(
|
let bob_btc_wallet = Arc::new(
|
||||||
swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone())
|
swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone(), config.bitcoin_network)
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
bitcoind
|
bitcoind
|
||||||
.mint(
|
.mint(
|
||||||
bob_btc_wallet.0.new_address().await.unwrap(),
|
bob_btc_wallet.inner.new_address().await.unwrap(),
|
||||||
btc_starting_balance,
|
btc_starting_balance,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -20,6 +20,7 @@ genawaiter = "0.99.1"
|
|||||||
miniscript = { version = "4", features = ["serde"] }
|
miniscript = { version = "4", features = ["serde"] }
|
||||||
monero = { version = "0.9", features = ["serde_support"] }
|
monero = { version = "0.9", features = ["serde_support"] }
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
|
rust_decimal = "1.8"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
sha2 = "0.9"
|
sha2 = "0.9"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
@ -15,7 +15,14 @@ pub use bitcoin::{util::psbt::PartiallySignedTransaction, *};
|
|||||||
pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature};
|
pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature};
|
||||||
pub use transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund};
|
pub use transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund};
|
||||||
|
|
||||||
pub const TX_FEE: u64 = 10_000;
|
// TODO: Configurable tx-fee (note: parties have to agree prior to swapping)
|
||||||
|
// Current reasoning:
|
||||||
|
// tx with largest weight (as determined by get_weight() upon broadcast in e2e
|
||||||
|
// test) = 609 assuming segwit and 60 sat/vB:
|
||||||
|
// (609 / 4) * 60 (sat/vB) = 9135 sats
|
||||||
|
// Recommended: Overpay a bit to ensure we don't have to wait too long for test
|
||||||
|
// runs.
|
||||||
|
pub const TX_FEE: u64 = 15_000;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct SecretKey {
|
pub struct SecretKey {
|
||||||
@ -212,6 +219,11 @@ pub trait GetRawTransaction {
|
|||||||
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction>;
|
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Network {
|
||||||
|
fn get_network(&self) -> bitcoin::Network;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result<SecretKey> {
|
pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result<SecretKey> {
|
||||||
let adaptor = Adaptor::<Sha256, Deterministic<Sha256>>::default();
|
let adaptor = Adaptor::<Sha256, Deterministic<Sha256>>::default();
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use crate::bitcoin::{
|
use crate::bitcoin::{
|
||||||
build_shared_output_descriptor, verify_sig, BuildTxLockPsbt, OutPoint, PublicKey, Txid, TX_FEE,
|
build_shared_output_descriptor, verify_sig, BuildTxLockPsbt, Network, OutPoint, PublicKey,
|
||||||
|
Txid, TX_FEE,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
util::{bip143::SigHashCache, psbt::PartiallySignedTransaction},
|
util::{bip143::SigHashCache, psbt::PartiallySignedTransaction},
|
||||||
Address, Amount, Network, SigHash, SigHashType, Transaction, TxIn, TxOut,
|
Address, Amount, SigHash, SigHashType, Transaction, TxIn, TxOut,
|
||||||
};
|
};
|
||||||
use ecdsa_fun::Signature;
|
use ecdsa_fun::Signature;
|
||||||
use miniscript::{Descriptor, NullCtx};
|
use miniscript::{Descriptor, NullCtx};
|
||||||
@ -20,11 +21,11 @@ pub struct TxLock {
|
|||||||
impl TxLock {
|
impl TxLock {
|
||||||
pub async fn new<W>(wallet: &W, amount: Amount, A: PublicKey, B: PublicKey) -> Result<Self>
|
pub async fn new<W>(wallet: &W, amount: Amount, A: PublicKey, B: PublicKey) -> Result<Self>
|
||||||
where
|
where
|
||||||
W: BuildTxLockPsbt,
|
W: BuildTxLockPsbt + Network,
|
||||||
{
|
{
|
||||||
let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
||||||
let address = lock_output_descriptor
|
let address = lock_output_descriptor
|
||||||
.address(Network::Regtest, NullCtx)
|
.address(wallet.get_network(), NullCtx)
|
||||||
.expect("can derive address from descriptor");
|
.expect("can derive address from descriptor");
|
||||||
|
|
||||||
// We construct a psbt for convenience
|
// We construct a psbt for convenience
|
||||||
|
@ -33,7 +33,7 @@ use tracing::error;
|
|||||||
|
|
||||||
pub mod message;
|
pub mod message;
|
||||||
use crate::{
|
use crate::{
|
||||||
bitcoin::{BlockHeight, GetRawTransaction, TransactionBlockHeight},
|
bitcoin::{BlockHeight, GetRawTransaction, Network, TransactionBlockHeight},
|
||||||
monero::{CreateWalletForOutput, WatchForTransfer},
|
monero::{CreateWalletForOutput, WatchForTransfer},
|
||||||
};
|
};
|
||||||
use ::bitcoin::{Transaction, Txid};
|
use ::bitcoin::{Transaction, Txid};
|
||||||
@ -267,7 +267,7 @@ where
|
|||||||
// send to one receive in the correct order.
|
// send to one receive in the correct order.
|
||||||
pub async fn next_state<
|
pub async fn next_state<
|
||||||
R: RngCore + CryptoRng,
|
R: RngCore + CryptoRng,
|
||||||
B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction,
|
B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction + Network,
|
||||||
M: CreateWalletForOutput + WatchForTransfer,
|
M: CreateWalletForOutput + WatchForTransfer,
|
||||||
T: SendMessage<Message> + ReceiveMessage<alice::Message>,
|
T: SendMessage<Message> + ReceiveMessage<alice::Message>,
|
||||||
>(
|
>(
|
||||||
@ -401,7 +401,7 @@ impl State0 {
|
|||||||
|
|
||||||
pub async fn receive<W>(self, wallet: &W, msg: alice::Message0) -> anyhow::Result<State1>
|
pub async fn receive<W>(self, wallet: &W, msg: alice::Message0) -> anyhow::Result<State1>
|
||||||
where
|
where
|
||||||
W: BuildTxLockPsbt,
|
W: BuildTxLockPsbt + Network,
|
||||||
{
|
{
|
||||||
msg.dleq_proof_s_a.verify(
|
msg.dleq_proof_s_a.verify(
|
||||||
msg.S_a_bitcoin.clone().into(),
|
msg.S_a_bitcoin.clone().into(),
|
||||||
|
@ -9,6 +9,7 @@ pub struct Config {
|
|||||||
pub monero_max_finality_time: Duration,
|
pub monero_max_finality_time: Duration,
|
||||||
pub bitcoin_refund_timelock: u32,
|
pub bitcoin_refund_timelock: u32,
|
||||||
pub bitcoin_punish_timelock: u32,
|
pub bitcoin_punish_timelock: u32,
|
||||||
|
pub bitcoin_network: ::bitcoin::Network,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@ -23,6 +24,7 @@ impl Config {
|
|||||||
* mainnet::MONERO_FINALITY_CONFIRMATIONS,
|
* mainnet::MONERO_FINALITY_CONFIRMATIONS,
|
||||||
bitcoin_refund_timelock: mainnet::BITCOIN_REFUND_TIMELOCK,
|
bitcoin_refund_timelock: mainnet::BITCOIN_REFUND_TIMELOCK,
|
||||||
bitcoin_punish_timelock: mainnet::BITCOIN_PUNISH_TIMELOCK,
|
bitcoin_punish_timelock: mainnet::BITCOIN_PUNISH_TIMELOCK,
|
||||||
|
bitcoin_network: ::bitcoin::Network::Bitcoin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +39,7 @@ impl Config {
|
|||||||
* regtest::MONERO_FINALITY_CONFIRMATIONS,
|
* regtest::MONERO_FINALITY_CONFIRMATIONS,
|
||||||
bitcoin_refund_timelock: regtest::BITCOIN_REFUND_TIMELOCK,
|
bitcoin_refund_timelock: regtest::BITCOIN_REFUND_TIMELOCK,
|
||||||
bitcoin_punish_timelock: regtest::BITCOIN_PUNISH_TIMELOCK,
|
bitcoin_punish_timelock: regtest::BITCOIN_PUNISH_TIMELOCK,
|
||||||
|
bitcoin_network: ::bitcoin::Network::Regtest,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,17 @@ use rand::{CryptoRng, RngCore};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::ops::{Add, Mul, Sub};
|
use std::ops::{Add, Mul, Sub};
|
||||||
|
|
||||||
|
use bitcoin::hashes::core::fmt::Formatter;
|
||||||
pub use curve25519_dalek::scalar::Scalar;
|
pub use curve25519_dalek::scalar::Scalar;
|
||||||
pub use monero::*;
|
pub use monero::*;
|
||||||
|
use rust_decimal::{
|
||||||
|
prelude::{FromPrimitive, ToPrimitive},
|
||||||
|
Decimal,
|
||||||
|
};
|
||||||
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
pub const MIN_CONFIRMATIONS: u32 = 10;
|
pub const MIN_CONFIRMATIONS: u32 = 10;
|
||||||
|
pub const PICONERO_OFFSET: u64 = 1_000_000_000_000;
|
||||||
|
|
||||||
pub fn random_private_key<R: RngCore + CryptoRng>(rng: &mut R) -> PrivateKey {
|
pub fn random_private_key<R: RngCore + CryptoRng>(rng: &mut R) -> PrivateKey {
|
||||||
let scalar = Scalar::random(rng);
|
let scalar = Scalar::random(rng);
|
||||||
@ -76,9 +83,20 @@ impl Amount {
|
|||||||
pub fn from_piconero(amount: u64) -> Self {
|
pub fn from_piconero(amount: u64) -> Self {
|
||||||
Amount(amount)
|
Amount(amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_piconero(&self) -> u64 {
|
pub fn as_piconero(&self) -> u64 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_monero(amount: &str) -> Result<Self> {
|
||||||
|
let decimal = Decimal::from_str(amount)?;
|
||||||
|
let piconeros_dec =
|
||||||
|
decimal.mul(Decimal::from_u64(PICONERO_OFFSET).expect("constant to fit into u64"));
|
||||||
|
let piconeros = piconeros_dec
|
||||||
|
.to_u64()
|
||||||
|
.ok_or_else(|| OverflowError(amount.to_owned()))?;
|
||||||
|
Ok(Amount(piconeros))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add for Amount {
|
impl Add for Amount {
|
||||||
@ -111,6 +129,16 @@ impl From<Amount> for u64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Amount {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut decimal = Decimal::from(self.0);
|
||||||
|
decimal
|
||||||
|
.set_scale(12)
|
||||||
|
.expect("12 is smaller than max precision of 28");
|
||||||
|
write!(f, "{} XMR", decimal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct TransferProof {
|
pub struct TransferProof {
|
||||||
tx_hash: TxHash,
|
tx_hash: TxHash,
|
||||||
@ -177,3 +205,70 @@ pub trait CreateWalletForOutput {
|
|||||||
private_view_key: PrivateViewKey,
|
private_view_key: PrivateViewKey,
|
||||||
) -> anyhow::Result<()>;
|
) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
|
||||||
|
#[error("Overflow, cannot convert {0} to u64")]
|
||||||
|
pub struct OverflowError(pub String);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_monero_min() {
|
||||||
|
let min_pics = 1;
|
||||||
|
let amount = Amount::from_piconero(min_pics);
|
||||||
|
let monero = amount.to_string();
|
||||||
|
assert_eq!("0.000000000001 XMR", monero);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_monero_one() {
|
||||||
|
let min_pics = 1000000000000;
|
||||||
|
let amount = Amount::from_piconero(min_pics);
|
||||||
|
let monero = amount.to_string();
|
||||||
|
assert_eq!("1.000000000000 XMR", monero);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_monero_max() {
|
||||||
|
let max_pics = 18_446_744_073_709_551_615;
|
||||||
|
let amount = Amount::from_piconero(max_pics);
|
||||||
|
let monero = amount.to_string();
|
||||||
|
assert_eq!("18446744.073709551615 XMR", monero);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_monero_min() {
|
||||||
|
let monero_min = "0.000000000001";
|
||||||
|
let amount = Amount::parse_monero(monero_min).unwrap();
|
||||||
|
let pics = amount.0;
|
||||||
|
assert_eq!(1, pics);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_monero() {
|
||||||
|
let monero = "123";
|
||||||
|
let amount = Amount::parse_monero(monero).unwrap();
|
||||||
|
let pics = amount.0;
|
||||||
|
assert_eq!(123000000000000, pics);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_monero_max() {
|
||||||
|
let monero = "18446744.073709551615";
|
||||||
|
let amount = Amount::parse_monero(monero).unwrap();
|
||||||
|
let pics = amount.0;
|
||||||
|
assert_eq!(18446744073709551615, pics);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_monero_overflows() {
|
||||||
|
let overflow_pics = "18446744.073709551616";
|
||||||
|
let error = Amount::parse_monero(overflow_pics).unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
error.downcast_ref::<OverflowError>().unwrap(),
|
||||||
|
&OverflowError(overflow_pics.to_owned())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,8 +7,8 @@ use reqwest::Url;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
use xmr_btc::bitcoin::{
|
use xmr_btc::bitcoin::{
|
||||||
BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TransactionBlockHeight,
|
BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, Network, SignTxLock,
|
||||||
TxLock, WatchForRawTransaction,
|
TransactionBlockHeight, TxLock, WatchForRawTransaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -162,3 +162,9 @@ impl TransactionBlockHeight for Wallet {
|
|||||||
.expect("transient errors to be retried")
|
.expect("transient errors to be retried")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Network for Wallet {
|
||||||
|
fn get_network(&self) -> bitcoin::Network {
|
||||||
|
bitcoin::Network::Regtest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user