mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-02-14 22:01:38 -05:00
saving: implementing internal api shared by cli and rpc server
This commit is contained in:
parent
229ee5a65b
commit
4413a8d489
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -1601,7 +1601,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
"itoa 1.0.1",
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1641,7 +1641,7 @@ dependencies = [
|
|||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
"httparse",
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa 1.0.1",
|
"itoa",
|
||||||
"pin-project-lite 0.2.9",
|
"pin-project-lite 0.2.9",
|
||||||
"socket2 0.4.7",
|
"socket2 0.4.7",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -1760,12 +1760,6 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "0.4.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -1902,7 +1896,7 @@ dependencies = [
|
|||||||
"soketto",
|
"soketto",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util 0.7.2",
|
"tokio-util 0.7.3",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
]
|
]
|
||||||
@ -2682,7 +2676,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f85842b073145726190373213c63f852020fb884c841a3a1f390637267a2fb8c"
|
checksum = "f85842b073145726190373213c63f852020fb884c841a3a1f390637267a2fb8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dtoa",
|
"dtoa",
|
||||||
"itoa 1.0.1",
|
"itoa",
|
||||||
"open-metrics-client-derive-text-encode",
|
"open-metrics-client-derive-text-encode",
|
||||||
"owning_ref",
|
"owning_ref",
|
||||||
]
|
]
|
||||||
@ -3676,7 +3670,7 @@ version = "1.0.89"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
|
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa 1.0.1",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -3688,7 +3682,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"itoa 1.0.1",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -3975,7 +3969,7 @@ dependencies = [
|
|||||||
"hashlink",
|
"hashlink",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"itoa 1.0.1",
|
"itoa",
|
||||||
"libc",
|
"libc",
|
||||||
"libsqlite3-sys",
|
"libsqlite3-sys",
|
||||||
"log",
|
"log",
|
||||||
@ -4330,7 +4324,7 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa 1.0.1",
|
"itoa",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
"time-macros",
|
"time-macros",
|
||||||
|
213
swap/src/api.rs
Normal file
213
swap/src/api.rs
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use comfy_table::Table;
|
||||||
|
use jsonrpsee::http_server::{HttpServerHandle};
|
||||||
|
use qrcode::render::unicode;
|
||||||
|
use qrcode::QrCode;
|
||||||
|
use std::cmp::min;
|
||||||
|
use crate::network::rendezvous::XmrBtcNamespace;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use libp2p::core::Multiaddr;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use crate::bitcoin::Amount;
|
||||||
|
use std::env;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use crate::bitcoin::TxLock;
|
||||||
|
use crate::cli::command::{parse_args_and_apply_defaults, Command, ParseResult, Options};
|
||||||
|
use crate::cli::{list_sellers, EventLoop, SellerStatus};
|
||||||
|
use crate::common::check_latest_version;
|
||||||
|
use crate::database::open_db;
|
||||||
|
use crate::env::Config;
|
||||||
|
use crate::libp2p_ext::MultiAddrExt;
|
||||||
|
use crate::network::quote::{BidQuote, ZeroQuoteReceived};
|
||||||
|
use crate::network::swarm;
|
||||||
|
use crate::protocol::bob;
|
||||||
|
use crate::protocol::bob::{BobState, Swap};
|
||||||
|
use crate::seed::Seed;
|
||||||
|
use crate::rpc;
|
||||||
|
use crate::{bitcoin, cli, monero};
|
||||||
|
use url::Url;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct InternalApi {
|
||||||
|
pub opts: Options,
|
||||||
|
pub params: Params,
|
||||||
|
pub cmd: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Default)]
|
||||||
|
pub struct Params {
|
||||||
|
pub bitcoin_electrum_rpc_url: Option<Url>,
|
||||||
|
pub bitcoin_target_block: Option<usize>,
|
||||||
|
pub seller: Option<Multiaddr>,
|
||||||
|
pub bitcoin_change_address: Option<bitcoin::Address>,
|
||||||
|
pub monero_receive_address: Option<monero::Address>,
|
||||||
|
pub monero_daemon_address: Option<String>,
|
||||||
|
pub tor_socks5_port: Option<u16>,
|
||||||
|
pub namespace: Option<XmrBtcNamespace>,
|
||||||
|
pub rendezvous_point: Option<Multiaddr>,
|
||||||
|
pub swap_id: Option<Uuid>,
|
||||||
|
pub server_address: Option<SocketAddr>,
|
||||||
|
pub amount: Option<Amount>,
|
||||||
|
pub address: Option<bitcoin::Address>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InternalApi {
|
||||||
|
pub async fn call() -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init_bitcoin_wallet(
|
||||||
|
electrum_rpc_url: Url,
|
||||||
|
seed: &Seed,
|
||||||
|
data_dir: PathBuf,
|
||||||
|
env_config: Config,
|
||||||
|
bitcoin_target_block: usize,
|
||||||
|
) -> Result<bitcoin::Wallet> {
|
||||||
|
let wallet_dir = data_dir.join("wallet");
|
||||||
|
|
||||||
|
let wallet = bitcoin::Wallet::new(
|
||||||
|
electrum_rpc_url.clone(),
|
||||||
|
&wallet_dir,
|
||||||
|
seed.derive_extended_private_key(env_config.bitcoin_network)?,
|
||||||
|
env_config,
|
||||||
|
bitcoin_target_block,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Failed to initialize Bitcoin wallet")?;
|
||||||
|
|
||||||
|
wallet.sync().await?;
|
||||||
|
|
||||||
|
Ok(wallet)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qr_code(value: &impl ToString) -> Result<String> {
|
||||||
|
let code = QrCode::new(value.to_string())?;
|
||||||
|
let qr_code = code
|
||||||
|
.render::<unicode::Dense1x2>()
|
||||||
|
.dark_color(unicode::Dense1x2::Light)
|
||||||
|
.light_color(unicode::Dense1x2::Dark)
|
||||||
|
.build();
|
||||||
|
Ok(qr_code)
|
||||||
|
}
|
||||||
|
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>>,
|
||||||
|
FB: Fn() -> TB,
|
||||||
|
TMG: Future<Output = Result<bitcoin::Amount>>,
|
||||||
|
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?;
|
||||||
|
|
||||||
|
if bid_quote.max_quantity == bitcoin::Amount::ZERO {
|
||||||
|
bail!(ZeroQuoteReceived)
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
price = %bid_quote.price,
|
||||||
|
minimum_amount = %bid_quote.min_quantity,
|
||||||
|
maximum_amount = %bid_quote.max_quantity,
|
||||||
|
"Received quote",
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut max_giveable = max_giveable_fn().await?;
|
||||||
|
|
||||||
|
if max_giveable == bitcoin::Amount::ZERO || max_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;
|
||||||
|
|
||||||
|
if !json {
|
||||||
|
eprintln!("{}", qr_code(&deposit_address)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
"Waiting for Bitcoin deposit",
|
||||||
|
);
|
||||||
|
|
||||||
|
max_giveable = loop {
|
||||||
|
sync().await?;
|
||||||
|
let new_max_givable = max_giveable_fn().await?;
|
||||||
|
|
||||||
|
if new_max_givable > max_giveable {
|
||||||
|
break new_max_givable;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_balance = balance().await?;
|
||||||
|
tracing::info!(%new_balance, %max_giveable, "Received Bitcoin");
|
||||||
|
|
||||||
|
if max_giveable < bid_quote.min_quantity {
|
||||||
|
tracing::info!("Deposited amount is less than `min_quantity`");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init_monero_wallet(
|
||||||
|
data_dir: PathBuf,
|
||||||
|
monero_daemon_address: String,
|
||||||
|
env_config: Config,
|
||||||
|
) -> Result<(monero::Wallet, monero::WalletRpcProcess)> {
|
||||||
|
let network = env_config.monero_network;
|
||||||
|
|
||||||
|
const MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME: &str = "swap-tool-blockchain-monitoring-wallet";
|
||||||
|
|
||||||
|
let monero_wallet_rpc = monero::WalletRpc::new(data_dir.join("monero")).await?;
|
||||||
|
|
||||||
|
let monero_wallet_rpc_process = monero_wallet_rpc
|
||||||
|
.run(network, monero_daemon_address.as_str())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let monero_wallet = monero::Wallet::open_or_create(
|
||||||
|
monero_wallet_rpc_process.endpoint(),
|
||||||
|
MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME.to_string(),
|
||||||
|
env_config,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok((monero_wallet, monero_wallet_rpc_process))
|
||||||
|
}
|
@ -26,7 +26,7 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use swap::bitcoin::TxLock;
|
use swap::bitcoin::TxLock;
|
||||||
use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command, ParseResult};
|
use swap::cli::command::{parse_args_and_apply_defaults, Options, Command, ParseResult};
|
||||||
use swap::cli::{list_sellers, EventLoop, SellerStatus};
|
use swap::cli::{list_sellers, EventLoop, SellerStatus};
|
||||||
use swap::common::check_latest_version;
|
use swap::common::check_latest_version;
|
||||||
use swap::database::open_db;
|
use swap::database::open_db;
|
||||||
@ -44,642 +44,10 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let Arguments {
|
|
||||||
env_config,
|
|
||||||
data_dir,
|
|
||||||
debug,
|
|
||||||
json,
|
|
||||||
cmd,
|
|
||||||
} = match parse_args_and_apply_defaults(env::args_os())? {
|
|
||||||
ParseResult::Arguments(args) => *args,
|
|
||||||
ParseResult::PrintAndExitZero { message } => {
|
|
||||||
println!("{}", message);
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = check_latest_version(env!("CARGO_PKG_VERSION")).await {
|
|
||||||
eprintln!("{}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
match cmd {
|
|
||||||
Command::BuyXmr {
|
|
||||||
seller,
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
bitcoin_change_address,
|
|
||||||
monero_receive_address,
|
|
||||||
monero_daemon_address,
|
|
||||||
tor_socks5_port,
|
|
||||||
namespace,
|
|
||||||
} => {
|
|
||||||
let swap_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
|
|
||||||
|
|
||||||
let db = open_db(data_dir.join("sqlite")).await?;
|
|
||||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
|
||||||
.context("Failed to read in seed file")?;
|
|
||||||
|
|
||||||
let bitcoin_wallet = init_bitcoin_wallet(
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
&seed,
|
|
||||||
data_dir.clone(),
|
|
||||||
env_config,
|
|
||||||
bitcoin_target_block,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let (monero_wallet, _process) =
|
|
||||||
init_monero_wallet(data_dir, monero_daemon_address, env_config).await?;
|
|
||||||
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
|
||||||
let seller_peer_id = seller
|
|
||||||
.extract_peer_id()
|
|
||||||
.context("Seller address must contain peer ID")?;
|
|
||||||
db.insert_address(seller_peer_id, seller.clone()).await?;
|
|
||||||
|
|
||||||
let behaviour = cli::Behaviour::new(
|
|
||||||
seller_peer_id,
|
|
||||||
env_config,
|
|
||||||
bitcoin_wallet.clone(),
|
|
||||||
(seed.derive_libp2p_identity(), namespace),
|
|
||||||
);
|
|
||||||
let mut swarm =
|
|
||||||
swarm::cli(seed.derive_libp2p_identity(), tor_socks5_port, behaviour).await?;
|
|
||||||
swarm.behaviour_mut().add_address(seller_peer_id, seller);
|
|
||||||
|
|
||||||
tracing::debug!(peer_id = %swarm.local_peer_id(), "Network layer initialized");
|
|
||||||
|
|
||||||
let (event_loop, mut event_loop_handle) =
|
|
||||||
EventLoop::new(swap_id, swarm, seller_peer_id)?;
|
|
||||||
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(),
|
|
||||||
bitcoin_wallet.new_address(),
|
|
||||||
|| bitcoin_wallet.balance(),
|
|
||||||
max_givable,
|
|
||||||
|| bitcoin_wallet.sync(),
|
|
||||||
estimate_fee,
|
|
||||||
)
|
|
||||||
.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");
|
|
||||||
|
|
||||||
db.insert_peer_id(swap_id, seller_peer_id).await?;
|
|
||||||
db.insert_monero_address(swap_id, monero_receive_address)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let swap = Swap::new(
|
|
||||||
db,
|
|
||||||
swap_id,
|
|
||||||
bitcoin_wallet,
|
|
||||||
Arc::new(monero_wallet),
|
|
||||||
env_config,
|
|
||||||
event_loop_handle,
|
|
||||||
monero_receive_address,
|
|
||||||
bitcoin_change_address,
|
|
||||||
amount,
|
|
||||||
);
|
|
||||||
|
|
||||||
tokio::select! {
|
|
||||||
result = event_loop => {
|
|
||||||
result
|
|
||||||
.context("EventLoop panicked")?;
|
|
||||||
},
|
|
||||||
result = bob::run(swap) => {
|
|
||||||
result.context("Failed to complete swap")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Command::History => {
|
|
||||||
cli::tracing::init(debug, json, data_dir.join("logs"), None)?;
|
|
||||||
|
|
||||||
let db = open_db(data_dir.join("sqlite")).await?;
|
|
||||||
let swaps = db.all().await?;
|
|
||||||
|
|
||||||
if json {
|
|
||||||
for (swap_id, state) in swaps {
|
|
||||||
let state: BobState = state.try_into()?;
|
|
||||||
tracing::info!(swap_id=%swap_id.to_string(), state=%state.to_string(), "Read swap state from database");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut table = Table::new();
|
|
||||||
|
|
||||||
table.set_header(vec!["SWAP ID", "STATE"]);
|
|
||||||
|
|
||||||
for (swap_id, state) in swaps {
|
|
||||||
let state: BobState = state.try_into()?;
|
|
||||||
table.add_row(vec![swap_id.to_string(), state.to_string()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{}", table);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Command::Config => {
|
|
||||||
cli::tracing::init(debug, json, data_dir.join("logs"), None)?;
|
|
||||||
|
|
||||||
tracing::info!(path=%data_dir.display(), "Data directory");
|
|
||||||
tracing::info!(path=%format!("{}/logs", data_dir.display()), "Log files directory");
|
|
||||||
tracing::info!(path=%format!("{}/sqlite", data_dir.display()), "Sqlite file location");
|
|
||||||
tracing::info!(path=%format!("{}/seed.pem", data_dir.display()), "Seed file location");
|
|
||||||
tracing::info!(path=%format!("{}/monero", data_dir.display()), "Monero-wallet-rpc directory");
|
|
||||||
tracing::info!(path=%format!("{}/wallet", data_dir.display()), "Internal bitcoin wallet directory");
|
|
||||||
}
|
|
||||||
Command::WithdrawBtc {
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
amount,
|
|
||||||
address,
|
|
||||||
} => {
|
|
||||||
cli::tracing::init(debug, json, data_dir.join("logs"), None)?;
|
|
||||||
|
|
||||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
|
||||||
.context("Failed to read in seed file")?;
|
|
||||||
let bitcoin_wallet = init_bitcoin_wallet(
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
&seed,
|
|
||||||
data_dir.clone(),
|
|
||||||
env_config,
|
|
||||||
bitcoin_target_block,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let amount = match amount {
|
|
||||||
Some(amount) => amount,
|
|
||||||
None => {
|
|
||||||
bitcoin_wallet
|
|
||||||
.max_giveable(address.script_pubkey().len())
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let psbt = bitcoin_wallet
|
|
||||||
.send_to_address(address, amount, None)
|
|
||||||
.await?;
|
|
||||||
let signed_tx = bitcoin_wallet.sign_and_finalize(psbt).await?;
|
|
||||||
|
|
||||||
bitcoin_wallet.broadcast(signed_tx, "withdraw").await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Command::Balance {
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
} => {
|
|
||||||
cli::tracing::init(debug, json, data_dir.join("logs"), None)?;
|
|
||||||
|
|
||||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
|
||||||
.context("Failed to read in seed file")?;
|
|
||||||
let bitcoin_wallet = init_bitcoin_wallet(
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
&seed,
|
|
||||||
data_dir.clone(),
|
|
||||||
env_config,
|
|
||||||
bitcoin_target_block,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let bitcoin_balance = bitcoin_wallet.balance().await?;
|
|
||||||
tracing::info!(
|
|
||||||
balance = %bitcoin_balance,
|
|
||||||
"Checked Bitcoin balance",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Command::StartDaemon {
|
|
||||||
server_address,
|
|
||||||
} => {
|
|
||||||
let handle = rpc::run_server(server_address).await?;
|
|
||||||
loop {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
Command::Resume {
|
|
||||||
swap_id,
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
monero_daemon_address,
|
|
||||||
tor_socks5_port,
|
|
||||||
namespace,
|
|
||||||
} => {
|
|
||||||
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
|
|
||||||
|
|
||||||
let db = open_db(data_dir.join("sqlite")).await?;
|
|
||||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
|
||||||
.context("Failed to read in seed file")?;
|
|
||||||
|
|
||||||
let bitcoin_wallet = init_bitcoin_wallet(
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
&seed,
|
|
||||||
data_dir.clone(),
|
|
||||||
env_config,
|
|
||||||
bitcoin_target_block,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let (monero_wallet, _process) =
|
|
||||||
init_monero_wallet(data_dir, monero_daemon_address, env_config).await?;
|
|
||||||
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
|
||||||
|
|
||||||
let seller_peer_id = db.get_peer_id(swap_id).await?;
|
|
||||||
let seller_addresses = db.get_addresses(seller_peer_id).await?;
|
|
||||||
|
|
||||||
let behaviour = cli::Behaviour::new(
|
|
||||||
seller_peer_id,
|
|
||||||
env_config,
|
|
||||||
bitcoin_wallet.clone(),
|
|
||||||
(seed.derive_libp2p_identity(), namespace),
|
|
||||||
);
|
|
||||||
let mut swarm =
|
|
||||||
swarm::cli(seed.derive_libp2p_identity(), tor_socks5_port, behaviour).await?;
|
|
||||||
let our_peer_id = swarm.local_peer_id();
|
|
||||||
tracing::debug!(peer_id = %our_peer_id, "Network layer initialized");
|
|
||||||
|
|
||||||
for seller_address in seller_addresses {
|
|
||||||
swarm
|
|
||||||
.behaviour_mut()
|
|
||||||
.add_address(seller_peer_id, seller_address);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (event_loop, event_loop_handle) = EventLoop::new(swap_id, swarm, seller_peer_id)?;
|
|
||||||
let handle = tokio::spawn(event_loop.run());
|
|
||||||
|
|
||||||
let monero_receive_address = db.get_monero_address(swap_id).await?;
|
|
||||||
let swap = Swap::from_db(
|
|
||||||
db,
|
|
||||||
swap_id,
|
|
||||||
bitcoin_wallet,
|
|
||||||
Arc::new(monero_wallet),
|
|
||||||
env_config,
|
|
||||||
event_loop_handle,
|
|
||||||
monero_receive_address,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
tokio::select! {
|
|
||||||
event_loop_result = handle => {
|
|
||||||
event_loop_result?;
|
|
||||||
},
|
|
||||||
swap_result = bob::run(swap) => {
|
|
||||||
swap_result?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Command::Cancel {
|
|
||||||
swap_id,
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
} => {
|
|
||||||
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
|
|
||||||
|
|
||||||
let db = open_db(data_dir.join("sqlite")).await?;
|
|
||||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
|
||||||
.context("Failed to read in seed file")?;
|
|
||||||
|
|
||||||
let bitcoin_wallet = init_bitcoin_wallet(
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
&seed,
|
|
||||||
data_dir,
|
|
||||||
env_config,
|
|
||||||
bitcoin_target_block,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let (txid, _) = cli::cancel(swap_id, Arc::new(bitcoin_wallet), db).await?;
|
|
||||||
tracing::debug!("Cancel transaction successfully published with id {}", txid);
|
|
||||||
}
|
|
||||||
Command::Refund {
|
|
||||||
swap_id,
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
} => {
|
|
||||||
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
|
|
||||||
|
|
||||||
let db = open_db(data_dir.join("sqlite")).await?;
|
|
||||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
|
||||||
.context("Failed to read in seed file")?;
|
|
||||||
|
|
||||||
let bitcoin_wallet = init_bitcoin_wallet(
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
&seed,
|
|
||||||
data_dir,
|
|
||||||
env_config,
|
|
||||||
bitcoin_target_block,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
cli::refund(swap_id, Arc::new(bitcoin_wallet), db).await?;
|
|
||||||
}
|
|
||||||
Command::ListSellers {
|
|
||||||
rendezvous_point,
|
|
||||||
namespace,
|
|
||||||
tor_socks5_port,
|
|
||||||
} => {
|
|
||||||
let rendezvous_node_peer_id = rendezvous_point
|
|
||||||
.extract_peer_id()
|
|
||||||
.context("Rendezvous node address must contain peer ID")?;
|
|
||||||
|
|
||||||
cli::tracing::init(debug, json, data_dir.join("logs"), None)?;
|
|
||||||
|
|
||||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
|
||||||
.context("Failed to read in seed file")?;
|
|
||||||
let identity = seed.derive_libp2p_identity();
|
|
||||||
|
|
||||||
let sellers = list_sellers(
|
|
||||||
rendezvous_node_peer_id,
|
|
||||||
rendezvous_point,
|
|
||||||
namespace,
|
|
||||||
tor_socks5_port,
|
|
||||||
identity,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if json {
|
|
||||||
for seller in sellers {
|
|
||||||
match seller.status {
|
|
||||||
SellerStatus::Online(quote) => {
|
|
||||||
tracing::info!(
|
|
||||||
price = %quote.price.to_string(),
|
|
||||||
min_quantity = %quote.min_quantity.to_string(),
|
|
||||||
max_quantity = %quote.max_quantity.to_string(),
|
|
||||||
status = "Online",
|
|
||||||
address = %seller.multiaddr.to_string(),
|
|
||||||
"Fetched peer status"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
SellerStatus::Unreachable => {
|
|
||||||
tracing::info!(
|
|
||||||
status = "Unreachable",
|
|
||||||
address = %seller.multiaddr.to_string(),
|
|
||||||
"Fetched peer status"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut table = Table::new();
|
|
||||||
|
|
||||||
table.set_header(vec![
|
|
||||||
"PRICE",
|
|
||||||
"MIN_QUANTITY",
|
|
||||||
"MAX_QUANTITY",
|
|
||||||
"STATUS",
|
|
||||||
"ADDRESS",
|
|
||||||
]);
|
|
||||||
|
|
||||||
for seller in sellers {
|
|
||||||
let row = match seller.status {
|
|
||||||
SellerStatus::Online(quote) => {
|
|
||||||
vec![
|
|
||||||
quote.price.to_string(),
|
|
||||||
quote.min_quantity.to_string(),
|
|
||||||
quote.max_quantity.to_string(),
|
|
||||||
"Online".to_owned(),
|
|
||||||
seller.multiaddr.to_string(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
SellerStatus::Unreachable => {
|
|
||||||
vec![
|
|
||||||
"???".to_owned(),
|
|
||||||
"???".to_owned(),
|
|
||||||
"???".to_owned(),
|
|
||||||
"Unreachable".to_owned(),
|
|
||||||
seller.multiaddr.to_string(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
table.add_row(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{}", table);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Command::ExportBitcoinWallet {
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
} => {
|
|
||||||
cli::tracing::init(debug, json, data_dir.join("logs"), None)?;
|
|
||||||
|
|
||||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
|
||||||
.context("Failed to read in seed file")?;
|
|
||||||
let bitcoin_wallet = init_bitcoin_wallet(
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
&seed,
|
|
||||||
data_dir.clone(),
|
|
||||||
env_config,
|
|
||||||
bitcoin_target_block,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let wallet_export = bitcoin_wallet.wallet_export("cli").await?;
|
|
||||||
tracing::info!(descriptor=%wallet_export.to_string(), "Exported bitcoin wallet");
|
|
||||||
}
|
|
||||||
Command::MoneroRecovery { swap_id } => {
|
|
||||||
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
|
|
||||||
|
|
||||||
let db = open_db(data_dir.join("sqlite")).await?;
|
|
||||||
|
|
||||||
let swap_state: BobState = db.get_state(swap_id).await?.try_into()?;
|
|
||||||
|
|
||||||
match swap_state {
|
|
||||||
BobState::Started { .. }
|
|
||||||
| BobState::SwapSetupCompleted(_)
|
|
||||||
| BobState::BtcLocked { .. }
|
|
||||||
| BobState::XmrLockProofReceived { .. }
|
|
||||||
| BobState::XmrLocked(_)
|
|
||||||
| BobState::EncSigSent(_)
|
|
||||||
| BobState::CancelTimelockExpired(_)
|
|
||||||
| BobState::BtcCancelled(_)
|
|
||||||
| BobState::BtcRefunded(_)
|
|
||||||
| BobState::BtcPunished { .. }
|
|
||||||
| BobState::SafelyAborted
|
|
||||||
| BobState::XmrRedeemed { .. } => {
|
|
||||||
bail!("Cannot print monero recovery information in state {}, only possible for BtcRedeemed", swap_state)
|
|
||||||
}
|
|
||||||
BobState::BtcRedeemed(state5) => {
|
|
||||||
let (spend_key, view_key) = state5.xmr_keys();
|
|
||||||
|
|
||||||
let address = monero::Address::standard(
|
|
||||||
env_config.monero_network,
|
|
||||||
monero::PublicKey::from_private_key(&spend_key),
|
|
||||||
monero::PublicKey::from(view_key.public()),
|
|
||||||
);
|
|
||||||
tracing::info!("Wallet address: {}", address.to_string());
|
|
||||||
|
|
||||||
let view_key = serde_json::to_string(&view_key)?;
|
|
||||||
println!("View key: {}", view_key);
|
|
||||||
|
|
||||||
println!("Spend key: {}", spend_key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn init_bitcoin_wallet(
|
|
||||||
electrum_rpc_url: Url,
|
|
||||||
seed: &Seed,
|
|
||||||
data_dir: PathBuf,
|
|
||||||
env_config: Config,
|
|
||||||
bitcoin_target_block: usize,
|
|
||||||
) -> Result<bitcoin::Wallet> {
|
|
||||||
let xprivkey = seed.derive_extended_private_key(env_config.bitcoin_network)?;
|
|
||||||
|
|
||||||
let wallet = bitcoin::Wallet::new(
|
|
||||||
electrum_rpc_url.clone(),
|
|
||||||
data_dir,
|
|
||||||
xprivkey,
|
|
||||||
env_config,
|
|
||||||
bitcoin_target_block,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Failed to initialize Bitcoin wallet")?;
|
|
||||||
|
|
||||||
wallet.sync().await?;
|
|
||||||
|
|
||||||
Ok(wallet)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn init_monero_wallet(
|
|
||||||
data_dir: PathBuf,
|
|
||||||
monero_daemon_address: String,
|
|
||||||
env_config: Config,
|
|
||||||
) -> Result<(monero::Wallet, monero::WalletRpcProcess)> {
|
|
||||||
let network = env_config.monero_network;
|
|
||||||
|
|
||||||
const MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME: &str = "swap-tool-blockchain-monitoring-wallet";
|
|
||||||
|
|
||||||
let monero_wallet_rpc = monero::WalletRpc::new(data_dir.join("monero")).await?;
|
|
||||||
|
|
||||||
let monero_wallet_rpc_process = monero_wallet_rpc
|
|
||||||
.run(network, monero_daemon_address.as_str())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let monero_wallet = monero::Wallet::open_or_create(
|
|
||||||
monero_wallet_rpc_process.endpoint(),
|
|
||||||
MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME.to_string(),
|
|
||||||
env_config,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok((monero_wallet, monero_wallet_rpc_process))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn qr_code(value: &impl ToString) -> Result<String> {
|
|
||||||
let code = QrCode::new(value.to_string())?;
|
|
||||||
let qr_code = code
|
|
||||||
.render::<unicode::Dense1x2>()
|
|
||||||
.dark_color(unicode::Dense1x2::Light)
|
|
||||||
.light_color(unicode::Dense1x2::Dark)
|
|
||||||
.build();
|
|
||||||
Ok(qr_code)
|
|
||||||
}
|
|
||||||
|
|
||||||
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>>,
|
|
||||||
FB: Fn() -> TB,
|
|
||||||
TMG: Future<Output = Result<bitcoin::Amount>>,
|
|
||||||
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?;
|
|
||||||
|
|
||||||
if bid_quote.max_quantity == bitcoin::Amount::ZERO {
|
|
||||||
bail!(ZeroQuoteReceived)
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::info!(
|
|
||||||
price = %bid_quote.price,
|
|
||||||
minimum_amount = %bid_quote.min_quantity,
|
|
||||||
maximum_amount = %bid_quote.max_quantity,
|
|
||||||
"Received quote",
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut max_giveable = max_giveable_fn().await?;
|
|
||||||
|
|
||||||
if max_giveable == bitcoin::Amount::ZERO || max_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;
|
|
||||||
|
|
||||||
if !json {
|
|
||||||
eprintln!("{}", qr_code(&deposit_address)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
"Waiting for Bitcoin deposit",
|
|
||||||
);
|
|
||||||
|
|
||||||
max_giveable = loop {
|
|
||||||
sync().await?;
|
|
||||||
let new_max_givable = max_giveable_fn().await?;
|
|
||||||
|
|
||||||
if new_max_givable > max_giveable {
|
|
||||||
break new_max_givable;
|
|
||||||
}
|
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_balance = balance().await?;
|
|
||||||
tracing::info!(%new_balance, %max_giveable, "Received Bitcoin");
|
|
||||||
|
|
||||||
if max_giveable < bid_quote.min_quantity {
|
|
||||||
tracing::info!("Deposited amount is less than `min_quantity`");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -3,6 +3,7 @@ use crate::env::GetConfig;
|
|||||||
use crate::fs::system_data_dir;
|
use crate::fs::system_data_dir;
|
||||||
use crate::network::rendezvous::XmrBtcNamespace;
|
use crate::network::rendezvous::XmrBtcNamespace;
|
||||||
use crate::{env, monero};
|
use crate::{env, monero};
|
||||||
|
use crate::api::{InternalApi, Params};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use bitcoin::{Address, AddressType};
|
use bitcoin::{Address, AddressType};
|
||||||
use libp2p::core::Multiaddr;
|
use libp2p::core::Multiaddr;
|
||||||
@ -30,19 +31,18 @@ const DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET: usize = 1;
|
|||||||
const DEFAULT_TOR_SOCKS5_PORT: &str = "9050";
|
const DEFAULT_TOR_SOCKS5_PORT: &str = "9050";
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Arguments {
|
pub struct Options {
|
||||||
pub env_config: env::Config,
|
pub env_config: env::Config,
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub json: bool,
|
pub json: bool,
|
||||||
pub data_dir: PathBuf,
|
pub data_dir: PathBuf,
|
||||||
pub cmd: Command,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the result of parsing the command-line parameters.
|
/// Represents the result of parsing the command-line parameters.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum ParseResult {
|
pub enum ParseResult {
|
||||||
/// The arguments we were invoked in.
|
/// The arguments we were invoked in.
|
||||||
Arguments(Box<Arguments>),
|
InternalApi(Box<InternalApi>),
|
||||||
/// A flag or command was given that does not need further processing other
|
/// A flag or command was given that does not need further processing other
|
||||||
/// than printing the provided message.
|
/// than printing the provided message.
|
||||||
///
|
///
|
||||||
@ -70,7 +70,7 @@ where
|
|||||||
let is_testnet = args.testnet;
|
let is_testnet = args.testnet;
|
||||||
let data = args.data;
|
let data = args.data;
|
||||||
|
|
||||||
let arguments = match args.cmd {
|
let api = match args.cmd {
|
||||||
RawCommand::BuyXmr {
|
RawCommand::BuyXmr {
|
||||||
seller: Seller { seller },
|
seller: Seller { seller },
|
||||||
bitcoin,
|
bitcoin,
|
||||||
@ -87,35 +87,49 @@ where
|
|||||||
let bitcoin_change_address =
|
let bitcoin_change_address =
|
||||||
validate_bitcoin_address(bitcoin_change_address, is_testnet)?;
|
validate_bitcoin_address(bitcoin_change_address, is_testnet)?;
|
||||||
|
|
||||||
Arguments {
|
InternalApi {
|
||||||
|
opts: Options {
|
||||||
|
env_config: env_config_from(is_testnet),
|
||||||
|
debug,
|
||||||
|
json,
|
||||||
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
|
},
|
||||||
|
params: Params {
|
||||||
|
seller: Some(seller),
|
||||||
|
bitcoin_electrum_rpc_url: Some(bitcoin_electrum_rpc_url),
|
||||||
|
bitcoin_target_block: Some(bitcoin_target_block),
|
||||||
|
bitcoin_change_address: Some(bitcoin_change_address),
|
||||||
|
monero_receive_address: Some(monero_receive_address),
|
||||||
|
monero_daemon_address: Some(monero_daemon_address),
|
||||||
|
tor_socks5_port: Some(tor_socks5_port),
|
||||||
|
namespace: Some(XmrBtcNamespace::from_is_testnet(is_testnet)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cmd: Command::BuyXmr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RawCommand::History => InternalApi {
|
||||||
|
opts: Options {
|
||||||
env_config: env_config_from(is_testnet),
|
env_config: env_config_from(is_testnet),
|
||||||
debug,
|
debug,
|
||||||
json,
|
json,
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
cmd: Command::BuyXmr {
|
},
|
||||||
seller,
|
params: Params {
|
||||||
bitcoin_electrum_rpc_url,
|
..Default::default()
|
||||||
bitcoin_target_block,
|
},
|
||||||
bitcoin_change_address,
|
|
||||||
monero_receive_address,
|
|
||||||
monero_daemon_address,
|
|
||||||
tor_socks5_port,
|
|
||||||
namespace: XmrBtcNamespace::from_is_testnet(is_testnet),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RawCommand::History => Arguments {
|
|
||||||
env_config: env_config_from(is_testnet),
|
|
||||||
debug,
|
|
||||||
json,
|
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
|
||||||
cmd: Command::History,
|
cmd: Command::History,
|
||||||
},
|
},
|
||||||
RawCommand::Config => Arguments {
|
RawCommand::Config => InternalApi {
|
||||||
env_config: env_config_from(is_testnet),
|
opts: Options {
|
||||||
debug,
|
env_config: env_config_from(is_testnet),
|
||||||
json,
|
debug,
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
json,
|
||||||
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
|
},
|
||||||
|
params: Params {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
cmd: Command::Config,
|
cmd: Command::Config,
|
||||||
},
|
},
|
||||||
RawCommand::Balance {
|
RawCommand::Balance {
|
||||||
@ -128,31 +142,39 @@ where
|
|||||||
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
||||||
bitcoin.apply_defaults(is_testnet)?;
|
bitcoin.apply_defaults(is_testnet)?;
|
||||||
|
|
||||||
Arguments {
|
InternalApi {
|
||||||
env_config: env_config_from(is_testnet),
|
opts: Options {
|
||||||
debug,
|
env_config: env_config_from(is_testnet),
|
||||||
json,
|
debug,
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
json,
|
||||||
cmd: Command::Balance {
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
},
|
},
|
||||||
|
params: Params {
|
||||||
|
bitcoin_electrum_rpc_url: Some(bitcoin_electrum_rpc_url),
|
||||||
|
bitcoin_target_block: Some(bitcoin_target_block),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cmd: Command::Balance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RawCommand::StartDaemon {
|
RawCommand::StartDaemon {
|
||||||
server_address,
|
server_address,
|
||||||
} => {
|
} => {
|
||||||
let server_address = "127.0.0.1:1234".parse()?;
|
let server_address = "127.0.0.1:1234".parse()?;
|
||||||
Arguments {
|
InternalApi {
|
||||||
env_config: env_config_from(is_testnet),
|
opts: Options {
|
||||||
debug,
|
env_config: env_config_from(is_testnet),
|
||||||
json,
|
debug,
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
json,
|
||||||
cmd: Command::StartDaemon {
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
server_address,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
},
|
||||||
|
params: Params {
|
||||||
|
server_address: Some(server_address),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cmd: Command::StartDaemon,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RawCommand::WithdrawBtc {
|
RawCommand::WithdrawBtc {
|
||||||
bitcoin,
|
bitcoin,
|
||||||
@ -162,17 +184,21 @@ where
|
|||||||
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
||||||
bitcoin.apply_defaults(is_testnet)?;
|
bitcoin.apply_defaults(is_testnet)?;
|
||||||
|
|
||||||
Arguments {
|
InternalApi {
|
||||||
env_config: env_config_from(is_testnet),
|
opts: Options {
|
||||||
debug,
|
env_config: env_config_from(is_testnet),
|
||||||
json,
|
debug,
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
json,
|
||||||
cmd: Command::WithdrawBtc {
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
amount,
|
|
||||||
address: bitcoin_address(address, is_testnet)?,
|
|
||||||
},
|
},
|
||||||
|
params: Params {
|
||||||
|
bitcoin_electrum_rpc_url: Some(bitcoin_electrum_rpc_url),
|
||||||
|
bitcoin_target_block: Some(bitcoin_target_block),
|
||||||
|
amount,
|
||||||
|
address: Some(bitcoin_address(address, is_testnet)?),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cmd: Command::WithdrawBtc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RawCommand::Resume {
|
RawCommand::Resume {
|
||||||
@ -185,19 +211,24 @@ where
|
|||||||
bitcoin.apply_defaults(is_testnet)?;
|
bitcoin.apply_defaults(is_testnet)?;
|
||||||
let monero_daemon_address = monero.apply_defaults(is_testnet);
|
let monero_daemon_address = monero.apply_defaults(is_testnet);
|
||||||
|
|
||||||
Arguments {
|
InternalApi {
|
||||||
env_config: env_config_from(is_testnet),
|
opts: Options {
|
||||||
debug,
|
env_config: env_config_from(is_testnet),
|
||||||
json,
|
debug,
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
json,
|
||||||
cmd: Command::Resume {
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
swap_id,
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
monero_daemon_address,
|
|
||||||
tor_socks5_port,
|
|
||||||
namespace: XmrBtcNamespace::from_is_testnet(is_testnet),
|
|
||||||
},
|
},
|
||||||
|
params: Params {
|
||||||
|
swap_id: Some(swap_id),
|
||||||
|
bitcoin_electrum_rpc_url: Some(bitcoin_electrum_rpc_url),
|
||||||
|
bitcoin_target_block: Some(bitcoin_target_block),
|
||||||
|
monero_daemon_address: Some(monero_daemon_address),
|
||||||
|
tor_socks5_port: Some(tor_socks5_port),
|
||||||
|
namespace: Some(XmrBtcNamespace::from_is_testnet(is_testnet)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cmd: Command::Resume,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RawCommand::Cancel {
|
RawCommand::Cancel {
|
||||||
@ -207,16 +238,20 @@ where
|
|||||||
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
||||||
bitcoin.apply_defaults(is_testnet)?;
|
bitcoin.apply_defaults(is_testnet)?;
|
||||||
|
|
||||||
Arguments {
|
InternalApi {
|
||||||
env_config: env_config_from(is_testnet),
|
opts: Options {
|
||||||
debug,
|
env_config: env_config_from(is_testnet),
|
||||||
json,
|
debug,
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
json,
|
||||||
cmd: Command::Cancel {
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
swap_id,
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
},
|
},
|
||||||
|
params: Params {
|
||||||
|
swap_id: Some(swap_id),
|
||||||
|
bitcoin_electrum_rpc_url: Some(bitcoin_electrum_rpc_url),
|
||||||
|
bitcoin_target_block: Some(bitcoin_target_block),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cmd: Command::Cancel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RawCommand::Refund {
|
RawCommand::Refund {
|
||||||
@ -226,121 +261,94 @@ where
|
|||||||
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
||||||
bitcoin.apply_defaults(is_testnet)?;
|
bitcoin.apply_defaults(is_testnet)?;
|
||||||
|
|
||||||
Arguments {
|
InternalApi {
|
||||||
env_config: env_config_from(is_testnet),
|
opts: Options {
|
||||||
debug,
|
env_config: env_config_from(is_testnet),
|
||||||
json,
|
debug,
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
json,
|
||||||
cmd: Command::Refund {
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
swap_id,
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
},
|
},
|
||||||
|
params: Params {
|
||||||
|
swap_id: Some(swap_id),
|
||||||
|
bitcoin_electrum_rpc_url: Some(bitcoin_electrum_rpc_url),
|
||||||
|
bitcoin_target_block: Some(bitcoin_target_block),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cmd: Command::Refund,
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RawCommand::ListSellers {
|
RawCommand::ListSellers {
|
||||||
rendezvous_point,
|
rendezvous_point,
|
||||||
tor: Tor { tor_socks5_port },
|
tor: Tor { tor_socks5_port },
|
||||||
} => Arguments {
|
} => InternalApi {
|
||||||
env_config: env_config_from(is_testnet),
|
opts: Options {
|
||||||
debug,
|
env_config: env_config_from(is_testnet),
|
||||||
json,
|
debug,
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
json,
|
||||||
cmd: Command::ListSellers {
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
rendezvous_point,
|
|
||||||
tor_socks5_port,
|
|
||||||
namespace: XmrBtcNamespace::from_is_testnet(is_testnet),
|
|
||||||
},
|
},
|
||||||
|
params: Params {
|
||||||
|
rendezvous_point: Some(rendezvous_point),
|
||||||
|
tor_socks5_port: Some(tor_socks5_port),
|
||||||
|
namespace: Some(XmrBtcNamespace::from_is_testnet(is_testnet)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cmd: Command::ListSellers,
|
||||||
},
|
},
|
||||||
RawCommand::ExportBitcoinWallet { bitcoin } => {
|
RawCommand::ExportBitcoinWallet { bitcoin } => {
|
||||||
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
||||||
bitcoin.apply_defaults(is_testnet)?;
|
bitcoin.apply_defaults(is_testnet)?;
|
||||||
|
|
||||||
Arguments {
|
InternalApi {
|
||||||
|
opts: Options {
|
||||||
|
env_config: env_config_from(is_testnet),
|
||||||
|
debug,
|
||||||
|
json,
|
||||||
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
|
},
|
||||||
|
params: Params {
|
||||||
|
bitcoin_target_block: Some(bitcoin_target_block),
|
||||||
|
bitcoin_electrum_rpc_url: Some(bitcoin_electrum_rpc_url),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cmd: Command::ExportBitcoinWallet,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RawCommand::MoneroRecovery { swap_id } => InternalApi {
|
||||||
|
opts: Options {
|
||||||
env_config: env_config_from(is_testnet),
|
env_config: env_config_from(is_testnet),
|
||||||
debug,
|
debug,
|
||||||
json,
|
json,
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||||
cmd: Command::ExportBitcoinWallet {
|
|
||||||
bitcoin_electrum_rpc_url,
|
|
||||||
bitcoin_target_block,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RawCommand::MoneroRecovery { swap_id } => Arguments {
|
|
||||||
env_config: env_config_from(is_testnet),
|
|
||||||
debug,
|
|
||||||
json,
|
|
||||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
|
||||||
cmd: Command::MoneroRecovery {
|
|
||||||
swap_id: swap_id.swap_id,
|
|
||||||
},
|
},
|
||||||
|
params: Params {
|
||||||
|
swap_id: Some(swap_id.swap_id),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cmd: Command::MoneroRecovery,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ParseResult::Arguments(Box::new(arguments)))
|
Ok(ParseResult::InternalApi(Box::new(api)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
BuyXmr {
|
BuyXmr,
|
||||||
seller: Multiaddr,
|
|
||||||
bitcoin_electrum_rpc_url: Url,
|
|
||||||
bitcoin_target_block: usize,
|
|
||||||
bitcoin_change_address: bitcoin::Address,
|
|
||||||
monero_receive_address: monero::Address,
|
|
||||||
monero_daemon_address: String,
|
|
||||||
tor_socks5_port: u16,
|
|
||||||
namespace: XmrBtcNamespace,
|
|
||||||
},
|
|
||||||
History,
|
History,
|
||||||
Config,
|
Config,
|
||||||
WithdrawBtc {
|
WithdrawBtc,
|
||||||
bitcoin_electrum_rpc_url: Url,
|
Balance,
|
||||||
bitcoin_target_block: usize,
|
Resume,
|
||||||
amount: Option<Amount>,
|
Cancel,
|
||||||
address: Address,
|
Refund,
|
||||||
},
|
ListSellers,
|
||||||
Balance {
|
ExportBitcoinWallet,
|
||||||
bitcoin_electrum_rpc_url: Url,
|
MoneroRecovery,
|
||||||
bitcoin_target_block: usize,
|
StartDaemon,
|
||||||
},
|
|
||||||
StartDaemon {
|
|
||||||
server_address: SocketAddr,
|
|
||||||
|
|
||||||
},
|
|
||||||
Resume {
|
|
||||||
swap_id: Uuid,
|
|
||||||
bitcoin_electrum_rpc_url: Url,
|
|
||||||
bitcoin_target_block: usize,
|
|
||||||
monero_daemon_address: String,
|
|
||||||
tor_socks5_port: u16,
|
|
||||||
namespace: XmrBtcNamespace,
|
|
||||||
},
|
|
||||||
Cancel {
|
|
||||||
swap_id: Uuid,
|
|
||||||
bitcoin_electrum_rpc_url: Url,
|
|
||||||
bitcoin_target_block: usize,
|
|
||||||
},
|
|
||||||
Refund {
|
|
||||||
swap_id: Uuid,
|
|
||||||
bitcoin_electrum_rpc_url: Url,
|
|
||||||
bitcoin_target_block: usize,
|
|
||||||
},
|
|
||||||
ListSellers {
|
|
||||||
rendezvous_point: Multiaddr,
|
|
||||||
namespace: XmrBtcNamespace,
|
|
||||||
tor_socks5_port: u16,
|
|
||||||
},
|
|
||||||
ExportBitcoinWallet {
|
|
||||||
bitcoin_electrum_rpc_url: Url,
|
|
||||||
bitcoin_target_block: usize,
|
|
||||||
},
|
|
||||||
MoneroRecovery {
|
|
||||||
swap_id: Uuid,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(structopt::StructOpt, Debug)]
|
#[derive(structopt::StructOpt, Debug)]
|
||||||
#[structopt(
|
#[structopt(
|
||||||
name = "swap",
|
name = "swap",
|
||||||
|
@ -24,6 +24,7 @@ pub mod database;
|
|||||||
pub mod env;
|
pub mod env;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod kraken;
|
pub mod kraken;
|
||||||
|
pub mod api;
|
||||||
pub mod libp2p_ext;
|
pub mod libp2p_ext;
|
||||||
pub mod monero;
|
pub mod monero;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
|
16
swap/src/rpc/mod.rs
Normal file
16
swap/src/rpc/mod.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
use jsonrpsee::http_server::{RpcModule, HttpServerBuilder, HttpServerHandle};
|
||||||
|
|
||||||
|
pub async fn run_server(server_address: SocketAddr) -> anyhow::Result<(SocketAddr, HttpServerHandle)> {
|
||||||
|
let server = HttpServerBuilder::default().build(server_address).await?;
|
||||||
|
let mut module = RpcModule::new(());
|
||||||
|
module.register_async_method("balance", |_, _| get_balance())?;
|
||||||
|
|
||||||
|
let addr = server.local_addr()?;
|
||||||
|
let server_handle = server.start(module)?;
|
||||||
|
Ok((addr, server_handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_balance() -> Result<&'static str, jsonrpsee::core::Error> {
|
||||||
|
Ok("hey")
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user