mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-01-08 06:08:08 -05:00
split api module and propagate errors with rpc server
This commit is contained in:
parent
500684a43e
commit
a26822d85b
513
swap/src/api.rs
513
swap/src/api.rs
@ -1,5 +1,6 @@
|
|||||||
|
pub mod request;
|
||||||
use crate::bitcoin::{Amount, TxLock};
|
use crate::bitcoin::{Amount, TxLock};
|
||||||
use crate::cli::command::{Bitcoin, Command, Monero, Tor};
|
use crate::cli::command::{Bitcoin, Monero, Tor};
|
||||||
use crate::cli::{list_sellers, EventLoop, SellerStatus};
|
use crate::cli::{list_sellers, EventLoop, SellerStatus};
|
||||||
use crate::database::open_db;
|
use crate::database::open_db;
|
||||||
use crate::env::{Config as EnvConfig, GetConfig, Mainnet, Testnet};
|
use crate::env::{Config as EnvConfig, GetConfig, Mainnet, Testnet};
|
||||||
@ -33,23 +34,6 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
static START: Once = Once::new();
|
static START: Once = Once::new();
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
pub struct Request {
|
|
||||||
pub params: Params,
|
|
||||||
pub cmd: Command,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Debug)]
|
|
||||||
pub struct Params {
|
|
||||||
pub seller: Option<Multiaddr>,
|
|
||||||
pub bitcoin_change_address: Option<bitcoin::Address>,
|
|
||||||
pub monero_receive_address: Option<monero::Address>,
|
|
||||||
pub rendezvous_point: Option<Multiaddr>,
|
|
||||||
pub swap_id: Option<Uuid>,
|
|
||||||
pub amount: Option<Amount>,
|
|
||||||
pub address: Option<bitcoin::Address>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
tor_socks5_port: Option<u16>,
|
tor_socks5_port: Option<u16>,
|
||||||
@ -70,377 +54,6 @@ pub struct Context {
|
|||||||
pub config: Config,
|
pub config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
|
||||||
pub async fn call(&self, context: Arc<Context>) -> Result<serde_json::Value> {
|
|
||||||
let result = match self.cmd {
|
|
||||||
Command::BuyXmr => {
|
|
||||||
let swap_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
let seed = context.config.seed.as_ref().unwrap();
|
|
||||||
let env_config = context.config.env_config;
|
|
||||||
let btc = context.bitcoin_wallet.as_ref().unwrap();
|
|
||||||
let seller = self.params.seller.clone().unwrap();
|
|
||||||
let monero_receive_address = self.params.monero_receive_address.unwrap();
|
|
||||||
let bitcoin_change_address = self.params.bitcoin_change_address.clone().unwrap();
|
|
||||||
|
|
||||||
let bitcoin_wallet = btc;
|
|
||||||
let seller_peer_id = self
|
|
||||||
.params
|
|
||||||
.seller
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.extract_peer_id()
|
|
||||||
.context("Seller address must contain peer ID")?;
|
|
||||||
context
|
|
||||||
.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(), context.config.namespace),
|
|
||||||
);
|
|
||||||
let mut swarm = swarm::cli(
|
|
||||||
seed.derive_libp2p_identity(),
|
|
||||||
context.config.tor_socks5_port.unwrap(),
|
|
||||||
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(
|
|
||||||
context.config.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");
|
|
||||||
|
|
||||||
context.db.insert_peer_id(swap_id, seller_peer_id).await?;
|
|
||||||
context
|
|
||||||
.db
|
|
||||||
.insert_monero_address(swap_id, monero_receive_address)
|
|
||||||
.await?;
|
|
||||||
let monero_wallet = context.monero_wallet.as_ref().unwrap();
|
|
||||||
|
|
||||||
let swap = Swap::new(
|
|
||||||
Arc::clone(&context.db),
|
|
||||||
swap_id,
|
|
||||||
Arc::clone(&bitcoin_wallet),
|
|
||||||
Arc::clone(&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")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
json!({
|
|
||||||
"empty": "true"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Command::History => {
|
|
||||||
let swaps = context.db.all().await?;
|
|
||||||
let mut vec: Vec<(Uuid, String)> = Vec::new();
|
|
||||||
for (swap_id, state) in swaps {
|
|
||||||
let state: BobState = state.try_into()?;
|
|
||||||
vec.push((swap_id, state.to_string()));
|
|
||||||
}
|
|
||||||
json!({ "swaps": vec })
|
|
||||||
}
|
|
||||||
Command::Config => {
|
|
||||||
// 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");
|
|
||||||
|
|
||||||
json!({
|
|
||||||
"result": []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Command::WithdrawBtc => {
|
|
||||||
let bitcoin_wallet = context.bitcoin_wallet.as_ref().unwrap();
|
|
||||||
|
|
||||||
let address = self.params.address.clone().unwrap();
|
|
||||||
|
|
||||||
let amount = match self.params.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.clone(), "withdraw")
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
json!({
|
|
||||||
"signed_tx": signed_tx,
|
|
||||||
"amount": amount.as_sat(),
|
|
||||||
"txid": signed_tx.txid(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Command::StartDaemon => {
|
|
||||||
let addr2 = "127.0.0.1:1234".parse()?;
|
|
||||||
|
|
||||||
let server_handle = {
|
|
||||||
if let Some(addr) = context.config.server_address {
|
|
||||||
let (_addr, handle) = rpc::run_server(addr, context).await?;
|
|
||||||
Some(handle)
|
|
||||||
} else {
|
|
||||||
let (_addr, handle) = rpc::run_server(addr2, context).await?;
|
|
||||||
Some(handle)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
loop {}
|
|
||||||
json!({
|
|
||||||
"result": []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Command::Balance => {
|
|
||||||
let bitcoin_wallet = context.bitcoin_wallet.as_ref().unwrap();
|
|
||||||
|
|
||||||
bitcoin_wallet.sync().await?;
|
|
||||||
let bitcoin_balance = bitcoin_wallet.balance().await?;
|
|
||||||
tracing::info!(
|
|
||||||
balance = %bitcoin_balance,
|
|
||||||
"Checked Bitcoin balance",
|
|
||||||
);
|
|
||||||
|
|
||||||
json!({
|
|
||||||
"balance": bitcoin_balance.as_sat()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Command::Resume => {
|
|
||||||
let swap_id = self.params.swap_id.unwrap();
|
|
||||||
|
|
||||||
let seller_peer_id = context.db.get_peer_id(swap_id).await?;
|
|
||||||
let seller_addresses = context.db.get_addresses(seller_peer_id).await?;
|
|
||||||
|
|
||||||
let seed = context.config.seed.as_ref().unwrap().derive_libp2p_identity();
|
|
||||||
|
|
||||||
let behaviour = cli::Behaviour::new(
|
|
||||||
seller_peer_id,
|
|
||||||
context.config.env_config,
|
|
||||||
Arc::clone(context.bitcoin_wallet.as_ref().unwrap()),
|
|
||||||
(seed.clone(), context.config.namespace),
|
|
||||||
);
|
|
||||||
let mut swarm = swarm::cli(
|
|
||||||
seed.clone(),
|
|
||||||
context.config.tor_socks5_port.clone().unwrap(),
|
|
||||||
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 = context.db.get_monero_address(swap_id).await?;
|
|
||||||
let swap = Swap::from_db(
|
|
||||||
Arc::clone(&context.db),
|
|
||||||
swap_id,
|
|
||||||
Arc::clone(context.bitcoin_wallet.as_ref().unwrap()),
|
|
||||||
Arc::clone(context.monero_wallet.as_ref().unwrap()),
|
|
||||||
context.config.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?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
json!({
|
|
||||||
"result": []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Command::Cancel => {
|
|
||||||
let bitcoin_wallet = context.bitcoin_wallet.as_ref().unwrap();
|
|
||||||
|
|
||||||
let (txid, _) = cli::cancel(
|
|
||||||
self.params.swap_id.unwrap(),
|
|
||||||
Arc::clone(bitcoin_wallet),
|
|
||||||
Arc::clone(&context.db),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
tracing::debug!("Cancel transaction successfully published with id {}", txid);
|
|
||||||
|
|
||||||
json!({
|
|
||||||
"txid": txid,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Command::Refund => {
|
|
||||||
let bitcoin_wallet = context.bitcoin_wallet.as_ref().unwrap();
|
|
||||||
|
|
||||||
let state = cli::refund(
|
|
||||||
self.params.swap_id.unwrap(),
|
|
||||||
Arc::clone(bitcoin_wallet),
|
|
||||||
Arc::clone(&context.db),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
json!({ "result": state })
|
|
||||||
}
|
|
||||||
Command::ListSellers => {
|
|
||||||
let rendezvous_point = self.params.rendezvous_point.clone().unwrap();
|
|
||||||
let rendezvous_node_peer_id = rendezvous_point
|
|
||||||
.extract_peer_id()
|
|
||||||
.context("Rendezvous node address must contain peer ID")?;
|
|
||||||
|
|
||||||
let identity = context.config.seed.as_ref().unwrap().derive_libp2p_identity();
|
|
||||||
|
|
||||||
let sellers = list_sellers(
|
|
||||||
rendezvous_node_peer_id,
|
|
||||||
rendezvous_point,
|
|
||||||
context.config.namespace,
|
|
||||||
context.config.tor_socks5_port.unwrap(),
|
|
||||||
identity,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
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"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
json!({ "sellers": sellers })
|
|
||||||
}
|
|
||||||
Command::ExportBitcoinWallet => {
|
|
||||||
let bitcoin_wallet = context.bitcoin_wallet.as_ref().unwrap();
|
|
||||||
|
|
||||||
let wallet_export = bitcoin_wallet.wallet_export("cli").await?;
|
|
||||||
tracing::info!(descriptor=%wallet_export.to_string(), "Exported bitcoin wallet");
|
|
||||||
json!({
|
|
||||||
"result": []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Command::MoneroRecovery => {
|
|
||||||
let swap_state: BobState = context
|
|
||||||
.db
|
|
||||||
.get_state(self.params.swap_id.clone().unwrap())
|
|
||||||
.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(
|
|
||||||
context.config.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
json!({
|
|
||||||
"result": []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
pub async fn build(
|
pub async fn build(
|
||||||
@ -523,19 +136,6 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Context {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
// 3 is the number of fields in the struct.
|
|
||||||
let mut state = serializer.serialize_struct("Context", 3)?;
|
|
||||||
state.serialize_field("debug", &self.config.debug)?;
|
|
||||||
state.serialize_field("json", &self.config.json)?;
|
|
||||||
state.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Context {
|
impl fmt::Debug for Context {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "Testing {}", true)
|
write!(f, "Testing {}", true)
|
||||||
@ -566,107 +166,6 @@ async fn init_bitcoin_wallet(
|
|||||||
Ok(wallet)
|
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)
|
|
||||||
}
|
|
||||||
pub 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(
|
async fn init_monero_wallet(
|
||||||
data_dir: PathBuf,
|
data_dir: PathBuf,
|
||||||
@ -784,7 +283,7 @@ pub mod api_test {
|
|||||||
monero_receive_address: Some(monero_receive_address),
|
monero_receive_address: Some(monero_receive_address),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::BuyXmr,
|
cmd: Method::BuyXmr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -794,7 +293,7 @@ pub mod api_test {
|
|||||||
swap_id: Some(Uuid::from_str(SWAP_ID).unwrap()),
|
swap_id: Some(Uuid::from_str(SWAP_ID).unwrap()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::Resume,
|
cmd: Method::Resume,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -804,7 +303,7 @@ pub mod api_test {
|
|||||||
swap_id: Some(Uuid::from_str(SWAP_ID).unwrap()),
|
swap_id: Some(Uuid::from_str(SWAP_ID).unwrap()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::Cancel,
|
cmd: Method::Cancel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -814,7 +313,7 @@ pub mod api_test {
|
|||||||
swap_id: Some(Uuid::from_str(SWAP_ID).unwrap()),
|
swap_id: Some(Uuid::from_str(SWAP_ID).unwrap()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::Refund,
|
cmd: Method::Refund,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
540
swap/src/api/request.rs
Normal file
540
swap/src/api/request.rs
Normal file
@ -0,0 +1,540 @@
|
|||||||
|
use crate::bitcoin::{Amount, TxLock};
|
||||||
|
use crate::cli::command::{Bitcoin, Monero, Tor};
|
||||||
|
use crate::cli::{list_sellers, EventLoop, SellerStatus};
|
||||||
|
use crate::database::open_db;
|
||||||
|
use crate::env::{Config as EnvConfig, GetConfig, Mainnet, Testnet};
|
||||||
|
use crate::fs::system_data_dir;
|
||||||
|
use crate::libp2p_ext::MultiAddrExt;
|
||||||
|
use crate::network::quote::{BidQuote, ZeroQuoteReceived};
|
||||||
|
use crate::network::rendezvous::XmrBtcNamespace;
|
||||||
|
use crate::network::swarm;
|
||||||
|
use crate::protocol::bob::{BobState, Swap};
|
||||||
|
use crate::protocol::{bob, Database};
|
||||||
|
use crate::seed::Seed;
|
||||||
|
use crate::{bitcoin, cli, monero, rpc};
|
||||||
|
use anyhow::{bail, Context as AnyContext, Result};
|
||||||
|
use comfy_table::Table;
|
||||||
|
use libp2p::core::Multiaddr;
|
||||||
|
use qrcode::render::unicode;
|
||||||
|
use qrcode::QrCode;
|
||||||
|
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||||
|
use serde_json::json;
|
||||||
|
use std::cmp::min;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fmt;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use url::Url;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use crate::api::{Config, Context};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub struct Request {
|
||||||
|
pub params: Params,
|
||||||
|
pub cmd: Method,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Debug)]
|
||||||
|
pub struct Params {
|
||||||
|
pub seller: Option<Multiaddr>,
|
||||||
|
pub bitcoin_change_address: Option<bitcoin::Address>,
|
||||||
|
pub monero_receive_address: Option<monero::Address>,
|
||||||
|
pub rendezvous_point: Option<Multiaddr>,
|
||||||
|
pub swap_id: Option<Uuid>,
|
||||||
|
pub amount: Option<Amount>,
|
||||||
|
pub address: Option<bitcoin::Address>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Method {
|
||||||
|
BuyXmr,
|
||||||
|
History,
|
||||||
|
Config,
|
||||||
|
WithdrawBtc,
|
||||||
|
Balance,
|
||||||
|
Resume,
|
||||||
|
Cancel,
|
||||||
|
Refund,
|
||||||
|
ListSellers,
|
||||||
|
ExportBitcoinWallet,
|
||||||
|
MoneroRecovery,
|
||||||
|
StartDaemon,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request {
|
||||||
|
pub async fn call(&self, context: Arc<Context>) -> Result<serde_json::Value> {
|
||||||
|
let result = match self.cmd {
|
||||||
|
Method::BuyXmr => {
|
||||||
|
let swap_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
let seed = context.config.seed.as_ref().unwrap();
|
||||||
|
let env_config = context.config.env_config;
|
||||||
|
let btc = context.bitcoin_wallet.as_ref().unwrap();
|
||||||
|
let seller = self.params.seller.clone().unwrap();
|
||||||
|
let monero_receive_address = self.params.monero_receive_address.unwrap();
|
||||||
|
let bitcoin_change_address = self.params.bitcoin_change_address.clone().unwrap();
|
||||||
|
|
||||||
|
let bitcoin_wallet = btc;
|
||||||
|
let seller_peer_id = self
|
||||||
|
.params
|
||||||
|
.seller
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.extract_peer_id()
|
||||||
|
.context("Seller address must contain peer ID")?;
|
||||||
|
context
|
||||||
|
.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(), context.config.namespace),
|
||||||
|
);
|
||||||
|
let mut swarm = swarm::cli(
|
||||||
|
seed.derive_libp2p_identity(),
|
||||||
|
context.config.tor_socks5_port.unwrap(),
|
||||||
|
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(
|
||||||
|
context.config.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");
|
||||||
|
|
||||||
|
context.db.insert_peer_id(swap_id, seller_peer_id).await?;
|
||||||
|
context
|
||||||
|
.db
|
||||||
|
.insert_monero_address(swap_id, monero_receive_address)
|
||||||
|
.await?;
|
||||||
|
let monero_wallet = context.monero_wallet.as_ref().unwrap();
|
||||||
|
|
||||||
|
let swap = Swap::new(
|
||||||
|
Arc::clone(&context.db),
|
||||||
|
swap_id,
|
||||||
|
Arc::clone(&bitcoin_wallet),
|
||||||
|
Arc::clone(&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")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json!({
|
||||||
|
"empty": "true"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Method::History => {
|
||||||
|
let swaps = context.db.all().await?;
|
||||||
|
let mut vec: Vec<(Uuid, String)> = Vec::new();
|
||||||
|
for (swap_id, state) in swaps {
|
||||||
|
let state: BobState = state.try_into()?;
|
||||||
|
vec.push((swap_id, state.to_string()));
|
||||||
|
}
|
||||||
|
json!({ "swaps": vec })
|
||||||
|
}
|
||||||
|
Method::Config => {
|
||||||
|
// 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");
|
||||||
|
|
||||||
|
json!({
|
||||||
|
"result": []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Method::WithdrawBtc => {
|
||||||
|
let bitcoin_wallet = context.bitcoin_wallet.as_ref().unwrap();
|
||||||
|
|
||||||
|
let address = self.params.address.clone().unwrap();
|
||||||
|
|
||||||
|
let amount = match self.params.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.clone(), "withdraw")
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
json!({
|
||||||
|
"signed_tx": signed_tx,
|
||||||
|
"amount": amount.as_sat(),
|
||||||
|
"txid": signed_tx.txid(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Method::StartDaemon => {
|
||||||
|
let addr2 = "127.0.0.1:1234".parse()?;
|
||||||
|
|
||||||
|
let server_handle = {
|
||||||
|
if let Some(addr) = context.config.server_address {
|
||||||
|
let (_addr, handle) = rpc::run_server(addr, context).await?;
|
||||||
|
Some(handle)
|
||||||
|
} else {
|
||||||
|
let (_addr, handle) = rpc::run_server(addr2, context).await?;
|
||||||
|
Some(handle)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loop {}
|
||||||
|
json!({
|
||||||
|
"result": []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Method::Balance => {
|
||||||
|
let bitcoin_wallet = context.bitcoin_wallet.as_ref().unwrap();
|
||||||
|
|
||||||
|
bitcoin_wallet.sync().await?;
|
||||||
|
let bitcoin_balance = bitcoin_wallet.balance().await?;
|
||||||
|
tracing::info!(
|
||||||
|
balance = %bitcoin_balance,
|
||||||
|
"Checked Bitcoin balance",
|
||||||
|
);
|
||||||
|
|
||||||
|
json!({
|
||||||
|
"balance": bitcoin_balance.as_sat()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Method::Resume => {
|
||||||
|
let swap_id = self.params.swap_id.unwrap();
|
||||||
|
|
||||||
|
let seller_peer_id = context.db.get_peer_id(swap_id).await?;
|
||||||
|
let seller_addresses = context.db.get_addresses(seller_peer_id).await?;
|
||||||
|
|
||||||
|
let seed = context.config.seed.as_ref().unwrap().derive_libp2p_identity();
|
||||||
|
|
||||||
|
let behaviour = cli::Behaviour::new(
|
||||||
|
seller_peer_id,
|
||||||
|
context.config.env_config,
|
||||||
|
Arc::clone(context.bitcoin_wallet.as_ref().unwrap()),
|
||||||
|
(seed.clone(), context.config.namespace),
|
||||||
|
);
|
||||||
|
let mut swarm = swarm::cli(
|
||||||
|
seed.clone(),
|
||||||
|
context.config.tor_socks5_port.clone().unwrap(),
|
||||||
|
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 = context.db.get_monero_address(swap_id).await?;
|
||||||
|
let swap = Swap::from_db(
|
||||||
|
Arc::clone(&context.db),
|
||||||
|
swap_id,
|
||||||
|
Arc::clone(context.bitcoin_wallet.as_ref().unwrap()),
|
||||||
|
Arc::clone(context.monero_wallet.as_ref().unwrap()),
|
||||||
|
context.config.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?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json!({
|
||||||
|
"result": []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Method::Cancel => {
|
||||||
|
let bitcoin_wallet = context.bitcoin_wallet.as_ref().unwrap();
|
||||||
|
|
||||||
|
let (txid, _) = cli::cancel(
|
||||||
|
self.params.swap_id.unwrap(),
|
||||||
|
Arc::clone(bitcoin_wallet),
|
||||||
|
Arc::clone(&context.db),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::debug!("Cancel transaction successfully published with id {}", txid);
|
||||||
|
|
||||||
|
json!({
|
||||||
|
"txid": txid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Method::Refund => {
|
||||||
|
let bitcoin_wallet = context.bitcoin_wallet.as_ref().unwrap();
|
||||||
|
|
||||||
|
let state = cli::refund(
|
||||||
|
self.params.swap_id.unwrap(),
|
||||||
|
Arc::clone(bitcoin_wallet),
|
||||||
|
Arc::clone(&context.db),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
json!({ "result": state })
|
||||||
|
}
|
||||||
|
Method::ListSellers => {
|
||||||
|
let rendezvous_point = self.params.rendezvous_point.clone().unwrap();
|
||||||
|
let rendezvous_node_peer_id = rendezvous_point
|
||||||
|
.extract_peer_id()
|
||||||
|
.context("Rendezvous node address must contain peer ID")?;
|
||||||
|
|
||||||
|
let identity = context.config.seed.as_ref().unwrap().derive_libp2p_identity();
|
||||||
|
|
||||||
|
let sellers = list_sellers(
|
||||||
|
rendezvous_node_peer_id,
|
||||||
|
rendezvous_point,
|
||||||
|
context.config.namespace,
|
||||||
|
context.config.tor_socks5_port.unwrap(),
|
||||||
|
identity,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json!({ "sellers": sellers })
|
||||||
|
}
|
||||||
|
Method::ExportBitcoinWallet => {
|
||||||
|
let bitcoin_wallet = context.bitcoin_wallet.as_ref().unwrap();
|
||||||
|
|
||||||
|
let wallet_export = bitcoin_wallet.wallet_export("cli").await?;
|
||||||
|
tracing::info!(descriptor=%wallet_export.to_string(), "Exported bitcoin wallet");
|
||||||
|
json!({
|
||||||
|
"result": []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Method::MoneroRecovery => {
|
||||||
|
let swap_state: BobState = context
|
||||||
|
.db
|
||||||
|
.get_state(self.params.swap_id.clone().unwrap())
|
||||||
|
.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(
|
||||||
|
context.config.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json!({
|
||||||
|
"result": []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
pub 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))
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
use crate::api::{Context, Params, Request, Config};
|
use crate::api::{Context, Config};
|
||||||
|
use crate::api::request::{Request, Params, Method};
|
||||||
use crate::bitcoin::{Amount, bitcoin_address};
|
use crate::bitcoin::{Amount, bitcoin_address};
|
||||||
use crate::monero::monero_address;
|
use crate::monero::monero_address;
|
||||||
use crate::fs::system_data_dir;
|
use crate::fs::system_data_dir;
|
||||||
@ -48,8 +49,8 @@ where
|
|||||||
I: IntoIterator<Item = T>,
|
I: IntoIterator<Item = T>,
|
||||||
T: Into<OsString> + Clone,
|
T: Into<OsString> + Clone,
|
||||||
{
|
{
|
||||||
let args = match RawArguments::clap().get_matches_from_safe(raw_args) {
|
let args = match Arguments::clap().get_matches_from_safe(raw_args) {
|
||||||
Ok(matches) => RawArguments::from_clap(&matches),
|
Ok(matches) => Arguments::from_clap(&matches),
|
||||||
Err(clap::Error {
|
Err(clap::Error {
|
||||||
message,
|
message,
|
||||||
kind: clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed,
|
kind: clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed,
|
||||||
@ -64,7 +65,7 @@ where
|
|||||||
let data = args.data;
|
let data = args.data;
|
||||||
|
|
||||||
let (context, request) = match args.cmd {
|
let (context, request) = match args.cmd {
|
||||||
RawCommand::BuyXmr {
|
CliCommand::BuyXmr {
|
||||||
seller: Seller { seller },
|
seller: Seller { seller },
|
||||||
bitcoin,
|
bitcoin,
|
||||||
bitcoin_change_address,
|
bitcoin_change_address,
|
||||||
@ -94,31 +95,31 @@ where
|
|||||||
seller: Some(seller),
|
seller: Some(seller),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::BuyXmr,
|
cmd: Method::BuyXmr,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
RawCommand::History => {
|
CliCommand::History => {
|
||||||
let context =
|
let context =
|
||||||
Context::build(None, None, None, data, is_testnet, debug, json, None).await?;
|
Context::build(None, None, None, data, is_testnet, debug, json, None).await?;
|
||||||
|
|
||||||
let request = Request {
|
let request = Request {
|
||||||
params: Params::default(),
|
params: Params::default(),
|
||||||
cmd: Command::History,
|
cmd: Method::History,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
RawCommand::Config => {
|
CliCommand::Config => {
|
||||||
let context =
|
let context =
|
||||||
Context::build(None, None, None, data, is_testnet, debug, json, None).await?;
|
Context::build(None, None, None, data, is_testnet, debug, json, None).await?;
|
||||||
|
|
||||||
let request = Request {
|
let request = Request {
|
||||||
params: Params::default(),
|
params: Params::default(),
|
||||||
cmd: Command::Config,
|
cmd: Method::Config,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
RawCommand::Balance { bitcoin } => {
|
CliCommand::Balance { bitcoin } => {
|
||||||
let context = Context::build(
|
let context = Context::build(
|
||||||
Some(bitcoin),
|
Some(bitcoin),
|
||||||
None,
|
None,
|
||||||
@ -132,11 +133,11 @@ where
|
|||||||
.await?;
|
.await?;
|
||||||
let request = Request {
|
let request = Request {
|
||||||
params: Params::default(),
|
params: Params::default(),
|
||||||
cmd: Command::Balance,
|
cmd: Method::Balance,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
RawCommand::StartDaemon {
|
CliCommand::StartDaemon {
|
||||||
server_address,
|
server_address,
|
||||||
bitcoin,
|
bitcoin,
|
||||||
monero,
|
monero,
|
||||||
@ -155,11 +156,11 @@ where
|
|||||||
.await?;
|
.await?;
|
||||||
let request = Request {
|
let request = Request {
|
||||||
params: Params::default(),
|
params: Params::default(),
|
||||||
cmd: Command::StartDaemon,
|
cmd: Method::StartDaemon,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
RawCommand::WithdrawBtc {
|
CliCommand::WithdrawBtc {
|
||||||
bitcoin,
|
bitcoin,
|
||||||
amount,
|
amount,
|
||||||
address,
|
address,
|
||||||
@ -184,11 +185,11 @@ where
|
|||||||
address: Some(address),
|
address: Some(address),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::WithdrawBtc,
|
cmd: Method::WithdrawBtc,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
RawCommand::Resume {
|
CliCommand::Resume {
|
||||||
swap_id: SwapId { swap_id },
|
swap_id: SwapId { swap_id },
|
||||||
bitcoin,
|
bitcoin,
|
||||||
monero,
|
monero,
|
||||||
@ -210,11 +211,11 @@ where
|
|||||||
swap_id: Some(swap_id),
|
swap_id: Some(swap_id),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::Resume,
|
cmd: Method::Resume,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
RawCommand::Cancel {
|
CliCommand::Cancel {
|
||||||
swap_id: SwapId { swap_id },
|
swap_id: SwapId { swap_id },
|
||||||
bitcoin,
|
bitcoin,
|
||||||
tor,
|
tor,
|
||||||
@ -235,11 +236,11 @@ where
|
|||||||
swap_id: Some(swap_id),
|
swap_id: Some(swap_id),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::Cancel,
|
cmd: Method::Cancel,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
RawCommand::Refund {
|
CliCommand::Refund {
|
||||||
swap_id: SwapId { swap_id },
|
swap_id: SwapId { swap_id },
|
||||||
bitcoin,
|
bitcoin,
|
||||||
tor,
|
tor,
|
||||||
@ -260,11 +261,11 @@ where
|
|||||||
swap_id: Some(swap_id),
|
swap_id: Some(swap_id),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::Refund,
|
cmd: Method::Refund,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
RawCommand::ListSellers {
|
CliCommand::ListSellers {
|
||||||
rendezvous_point,
|
rendezvous_point,
|
||||||
tor,
|
tor,
|
||||||
} => {
|
} => {
|
||||||
@ -276,11 +277,11 @@ where
|
|||||||
rendezvous_point: Some(rendezvous_point),
|
rendezvous_point: Some(rendezvous_point),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::ListSellers,
|
cmd: Method::ListSellers,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
RawCommand::ExportBitcoinWallet { bitcoin } => {
|
CliCommand::ExportBitcoinWallet { bitcoin } => {
|
||||||
let context = Context::build(
|
let context = Context::build(
|
||||||
Some(bitcoin),
|
Some(bitcoin),
|
||||||
None,
|
None,
|
||||||
@ -294,11 +295,11 @@ where
|
|||||||
.await?;
|
.await?;
|
||||||
let request = Request {
|
let request = Request {
|
||||||
params: Params::default(),
|
params: Params::default(),
|
||||||
cmd: Command::ExportBitcoinWallet,
|
cmd: Method::ExportBitcoinWallet,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
RawCommand::MoneroRecovery { swap_id } => {
|
CliCommand::MoneroRecovery { swap_id } => {
|
||||||
let context =
|
let context =
|
||||||
Context::build(None, None, None, data, is_testnet, debug, json, None).await?;
|
Context::build(None, None, None, data, is_testnet, debug, json, None).await?;
|
||||||
|
|
||||||
@ -307,7 +308,7 @@ where
|
|||||||
swap_id: Some(swap_id.swap_id),
|
swap_id: Some(swap_id.swap_id),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::MoneroRecovery,
|
cmd: Method::MoneroRecovery,
|
||||||
};
|
};
|
||||||
(context, request)
|
(context, request)
|
||||||
}
|
}
|
||||||
@ -315,21 +316,6 @@ where
|
|||||||
|
|
||||||
Ok(ParseResult::Context(Arc::new(context), Box::new(request)))
|
Ok(ParseResult::Context(Arc::new(context), Box::new(request)))
|
||||||
}
|
}
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Command {
|
|
||||||
BuyXmr,
|
|
||||||
History,
|
|
||||||
Config,
|
|
||||||
WithdrawBtc,
|
|
||||||
Balance,
|
|
||||||
Resume,
|
|
||||||
Cancel,
|
|
||||||
Refund,
|
|
||||||
ListSellers,
|
|
||||||
ExportBitcoinWallet,
|
|
||||||
MoneroRecovery,
|
|
||||||
StartDaemon,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(structopt::StructOpt, Debug)]
|
#[derive(structopt::StructOpt, Debug)]
|
||||||
#[structopt(
|
#[structopt(
|
||||||
@ -338,7 +324,7 @@ pub enum Command {
|
|||||||
author,
|
author,
|
||||||
version = env!("VERGEN_GIT_SEMVER_LIGHTWEIGHT")
|
version = env!("VERGEN_GIT_SEMVER_LIGHTWEIGHT")
|
||||||
)]
|
)]
|
||||||
struct RawArguments {
|
struct Arguments {
|
||||||
// global is necessary to ensure that clap can match against testnet in subcommands
|
// global is necessary to ensure that clap can match against testnet in subcommands
|
||||||
#[structopt(
|
#[structopt(
|
||||||
long,
|
long,
|
||||||
@ -365,11 +351,11 @@ struct RawArguments {
|
|||||||
json: bool,
|
json: bool,
|
||||||
|
|
||||||
#[structopt(subcommand)]
|
#[structopt(subcommand)]
|
||||||
cmd: RawCommand,
|
cmd: CliCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(structopt::StructOpt, Debug)]
|
#[derive(structopt::StructOpt, Debug)]
|
||||||
enum RawCommand {
|
enum CliCommand {
|
||||||
/// Start a BTC for XMR swap
|
/// Start a BTC for XMR swap
|
||||||
BuyXmr {
|
BuyXmr {
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::api::{Context, Params, Request};
|
use crate::api::{Context};
|
||||||
use crate::cli::command::Command;
|
use crate::api::request::{Params, Request, Method};
|
||||||
use crate::rpc::Error;
|
//use crate::rpc::Error;
|
||||||
|
use anyhow::{Error, Result};
|
||||||
use crate::{bitcoin, monero};
|
use crate::{bitcoin, monero};
|
||||||
use jsonrpsee::http_server::RpcModule;
|
use jsonrpsee::http_server::RpcModule;
|
||||||
use libp2p::core::Multiaddr;
|
use libp2p::core::Multiaddr;
|
||||||
@ -14,16 +15,12 @@ pub fn register_modules(context: Arc<Context>) -> RpcModule<Arc<Context>> {
|
|||||||
let mut module = RpcModule::new(context);
|
let mut module = RpcModule::new(context);
|
||||||
module
|
module
|
||||||
.register_async_method("get_bitcoin_balance", |_, context| async move {
|
.register_async_method("get_bitcoin_balance", |_, context| async move {
|
||||||
get_bitcoin_balance(&context)
|
get_bitcoin_balance(&context).await
|
||||||
.await
|
|
||||||
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))
|
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
module
|
module
|
||||||
.register_async_method("get_history", |_, context| async move {
|
.register_async_method("get_history", |_, context| async move {
|
||||||
get_history(&context)
|
get_history(&context).await
|
||||||
.await
|
|
||||||
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))
|
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
module
|
module
|
||||||
@ -38,22 +35,24 @@ pub fn register_modules(context: Arc<Context>) -> RpcModule<Arc<Context>> {
|
|||||||
module
|
module
|
||||||
.register_async_method("get_seller", |params, context| async move {
|
.register_async_method("get_seller", |params, context| async move {
|
||||||
let params: HashMap<String, String> = params.parse()?;
|
let params: HashMap<String, String> = params.parse()?;
|
||||||
|
|
||||||
let swap_id = Uuid::from_str(params.get("swap_id").ok_or_else(|| {
|
let swap_id = Uuid::from_str(params.get("swap_id").ok_or_else(|| {
|
||||||
jsonrpsee_core::Error::Custom("Does not contain swap_id".to_string())
|
jsonrpsee_core::Error::Custom("Does not contain swap_id".to_string())
|
||||||
})?)
|
})?)
|
||||||
.unwrap();
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
|
|
||||||
let peerId = context
|
let peerId = context
|
||||||
.db
|
.db
|
||||||
.get_peer_id(swap_id)
|
.get_peer_id(swap_id)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
.unwrap();
|
|
||||||
let addresses = context
|
let addresses = context
|
||||||
.db
|
.db
|
||||||
.get_addresses(peerId)
|
.get_addresses(peerId)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
.unwrap();
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"peerId": peerId.to_base58(),
|
"peerId": peerId.to_base58(),
|
||||||
"addresses": addresses
|
"addresses": addresses
|
||||||
@ -63,16 +62,17 @@ pub fn register_modules(context: Arc<Context>) -> RpcModule<Arc<Context>> {
|
|||||||
module
|
module
|
||||||
.register_async_method("get_swap_start_date", |params, context| async move {
|
.register_async_method("get_swap_start_date", |params, context| async move {
|
||||||
let params: HashMap<String, String> = params.parse()?;
|
let params: HashMap<String, String> = params.parse()?;
|
||||||
|
|
||||||
let swap_id = Uuid::from_str(params.get("swap_id").ok_or_else(|| {
|
let swap_id = Uuid::from_str(params.get("swap_id").ok_or_else(|| {
|
||||||
jsonrpsee_core::Error::Custom("Does not contain swap_id".to_string())
|
jsonrpsee_core::Error::Custom("Does not contain swap_id".to_string())
|
||||||
})?)
|
})?)
|
||||||
.unwrap();
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
|
|
||||||
let start_date = context
|
let start_date = context
|
||||||
.db
|
.db
|
||||||
.get_swap_start_date(swap_id)
|
.get_swap_start_date(swap_id)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"start_date": start_date,
|
"start_date": start_date,
|
||||||
@ -82,18 +82,19 @@ pub fn register_modules(context: Arc<Context>) -> RpcModule<Arc<Context>> {
|
|||||||
module
|
module
|
||||||
.register_async_method("resume_swap", |params, context| async move {
|
.register_async_method("resume_swap", |params, context| async move {
|
||||||
let params: HashMap<String, String> = params.parse()?;
|
let params: HashMap<String, String> = params.parse()?;
|
||||||
|
|
||||||
let swap_id = Uuid::from_str(params.get("swap_id").ok_or_else(|| {
|
let swap_id = Uuid::from_str(params.get("swap_id").ok_or_else(|| {
|
||||||
jsonrpsee_core::Error::Custom("Does not contain swap_id".to_string())
|
jsonrpsee_core::Error::Custom("Does not contain swap_id".to_string())
|
||||||
})?)
|
})?)
|
||||||
.unwrap();
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
resume_swap(swap_id, &context)
|
|
||||||
.await
|
resume_swap(swap_id, &context).await
|
||||||
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))
|
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
module
|
module
|
||||||
.register_async_method("withdraw_btc", |params, context| async move {
|
.register_async_method("withdraw_btc", |params, context| async move {
|
||||||
let params: HashMap<String, String> = params.parse()?;
|
let params: HashMap<String, String> = params.parse()?;
|
||||||
|
|
||||||
let amount = if let Some(amount_str) = params.get("amount") {
|
let amount = if let Some(amount_str) = params.get("amount") {
|
||||||
Some(
|
Some(
|
||||||
::bitcoin::Amount::from_str_in(amount_str, ::bitcoin::Denomination::Bitcoin)
|
::bitcoin::Amount::from_str_in(amount_str, ::bitcoin::Denomination::Bitcoin)
|
||||||
@ -104,19 +105,20 @@ pub fn register_modules(context: Arc<Context>) -> RpcModule<Arc<Context>> {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let withdraw_address =
|
let withdraw_address =
|
||||||
bitcoin::Address::from_str(params.get("address").ok_or_else(|| {
|
bitcoin::Address::from_str(params.get("address").ok_or_else(|| {
|
||||||
jsonrpsee_core::Error::Custom("Does not contain address".to_string())
|
jsonrpsee_core::Error::Custom("Does not contain address".to_string())
|
||||||
})?)
|
})?)
|
||||||
.unwrap();
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
withdraw_btc(withdraw_address, amount, &context)
|
|
||||||
.await
|
withdraw_btc(withdraw_address, amount, &context).await
|
||||||
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))
|
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
module
|
module
|
||||||
.register_async_method("buy_xmr", |params, context| async move {
|
.register_async_method("buy_xmr", |params, context| async move {
|
||||||
let params: HashMap<String, String> = params.parse()?;
|
let params: HashMap<String, String> = params.parse()?;
|
||||||
|
|
||||||
let bitcoin_change_address = bitcoin::Address::from_str(
|
let bitcoin_change_address = bitcoin::Address::from_str(
|
||||||
params.get("bitcoin_change_address").ok_or_else(|| {
|
params.get("bitcoin_change_address").ok_or_else(|| {
|
||||||
jsonrpsee_core::Error::Custom(
|
jsonrpsee_core::Error::Custom(
|
||||||
@ -124,7 +126,8 @@ pub fn register_modules(context: Arc<Context>) -> RpcModule<Arc<Context>> {
|
|||||||
)
|
)
|
||||||
})?,
|
})?,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
|
|
||||||
let monero_receive_address = monero::Address::from_str(
|
let monero_receive_address = monero::Address::from_str(
|
||||||
params.get("monero_receive_address").ok_or_else(|| {
|
params.get("monero_receive_address").ok_or_else(|| {
|
||||||
jsonrpsee_core::Error::Custom(
|
jsonrpsee_core::Error::Custom(
|
||||||
@ -132,19 +135,19 @@ pub fn register_modules(context: Arc<Context>) -> RpcModule<Arc<Context>> {
|
|||||||
)
|
)
|
||||||
})?,
|
})?,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
|
|
||||||
let seller = Multiaddr::from_str(params.get("seller").ok_or_else(|| {
|
let seller = Multiaddr::from_str(params.get("seller").ok_or_else(|| {
|
||||||
jsonrpsee_core::Error::Custom("Does not contain seller".to_string())
|
jsonrpsee_core::Error::Custom("Does not contain seller".to_string())
|
||||||
})?)
|
})?)
|
||||||
.unwrap();
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
|
|
||||||
buy_xmr(
|
buy_xmr(
|
||||||
bitcoin_change_address,
|
bitcoin_change_address,
|
||||||
monero_receive_address,
|
monero_receive_address,
|
||||||
seller,
|
seller,
|
||||||
&context,
|
&context,
|
||||||
)
|
).await
|
||||||
.await
|
|
||||||
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))
|
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
module
|
module
|
||||||
@ -154,62 +157,71 @@ pub fn register_modules(context: Arc<Context>) -> RpcModule<Arc<Context>> {
|
|||||||
Multiaddr::from_str(params.get("rendezvous_point").ok_or_else(|| {
|
Multiaddr::from_str(params.get("rendezvous_point").ok_or_else(|| {
|
||||||
jsonrpsee_core::Error::Custom("Does not contain rendezvous_point".to_string())
|
jsonrpsee_core::Error::Custom("Does not contain rendezvous_point".to_string())
|
||||||
})?)
|
})?)
|
||||||
.unwrap();
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
list_sellers(rendezvous_point, &context)
|
|
||||||
.await
|
list_sellers(rendezvous_point, &context).await
|
||||||
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))
|
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
module
|
module
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_bitcoin_balance(context: &Arc<Context>) -> anyhow::Result<serde_json::Value, Error> {
|
async fn get_bitcoin_balance(context: &Arc<Context>) -> Result<serde_json::Value, jsonrpsee_core::Error> {
|
||||||
let request = Request {
|
let request = Request {
|
||||||
params: Params::default(),
|
params: Params::default(),
|
||||||
cmd: Command::Balance,
|
cmd: Method::Balance,
|
||||||
};
|
};
|
||||||
let balance = request.call(Arc::clone(context)).await.unwrap();
|
let balance = request.call(Arc::clone(context))
|
||||||
|
.await
|
||||||
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
|
|
||||||
Ok(balance)
|
Ok(balance)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_history(context: &Arc<Context>) -> anyhow::Result<serde_json::Value, Error> {
|
async fn get_history(context: &Arc<Context>) -> Result<serde_json::Value, jsonrpsee_core::Error> {
|
||||||
let request = Request {
|
let request = Request {
|
||||||
params: Params::default(),
|
params: Params::default(),
|
||||||
cmd: Command::History,
|
cmd: Method::History,
|
||||||
};
|
};
|
||||||
let history = request.call(Arc::clone(context)).await.unwrap();
|
let history = request.call(Arc::clone(context))
|
||||||
|
.await
|
||||||
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
|
|
||||||
Ok(history)
|
Ok(history)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resume_swap(
|
async fn resume_swap(
|
||||||
swap_id: Uuid,
|
swap_id: Uuid,
|
||||||
context: &Arc<Context>,
|
context: &Arc<Context>,
|
||||||
) -> anyhow::Result<serde_json::Value, Error> {
|
) -> Result<serde_json::Value, jsonrpsee_core::Error> {
|
||||||
let request = Request {
|
let request = Request {
|
||||||
params: Params {
|
params: Params {
|
||||||
swap_id: Some(swap_id),
|
swap_id: Some(swap_id),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::Resume,
|
cmd: Method::Resume,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = request.call(Arc::clone(context)).await.unwrap();
|
let result = request.call(Arc::clone(context))
|
||||||
|
.await
|
||||||
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
async fn withdraw_btc(
|
async fn withdraw_btc(
|
||||||
withdraw_address: bitcoin::Address,
|
withdraw_address: bitcoin::Address,
|
||||||
amount: Option<bitcoin::Amount>,
|
amount: Option<bitcoin::Amount>,
|
||||||
context: &Arc<Context>,
|
context: &Arc<Context>,
|
||||||
) -> anyhow::Result<serde_json::Value, Error> {
|
) -> Result<serde_json::Value, jsonrpsee_core::Error> {
|
||||||
let request = Request {
|
let request = Request {
|
||||||
params: Params {
|
params: Params {
|
||||||
amount,
|
amount,
|
||||||
address: Some(withdraw_address),
|
address: Some(withdraw_address),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::WithdrawBtc,
|
cmd: Method::WithdrawBtc,
|
||||||
};
|
};
|
||||||
let result = request.call(Arc::clone(context)).await.unwrap();
|
let result = request.call(Arc::clone(context))
|
||||||
|
.await
|
||||||
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +230,7 @@ async fn buy_xmr(
|
|||||||
monero_receive_address: monero::Address,
|
monero_receive_address: monero::Address,
|
||||||
seller: Multiaddr,
|
seller: Multiaddr,
|
||||||
context: &Arc<Context>,
|
context: &Arc<Context>,
|
||||||
) -> anyhow::Result<serde_json::Value, Error> {
|
) -> Result<serde_json::Value, jsonrpsee_core::Error> {
|
||||||
let request = Request {
|
let request = Request {
|
||||||
params: Params {
|
params: Params {
|
||||||
bitcoin_change_address: Some(bitcoin_change_address),
|
bitcoin_change_address: Some(bitcoin_change_address),
|
||||||
@ -226,23 +238,27 @@ async fn buy_xmr(
|
|||||||
seller: Some(seller),
|
seller: Some(seller),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::BuyXmr,
|
cmd: Method::BuyXmr,
|
||||||
};
|
};
|
||||||
let swap = request.call(Arc::clone(context)).await.unwrap();
|
let swap = request.call(Arc::clone(context))
|
||||||
|
.await
|
||||||
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
Ok(swap)
|
Ok(swap)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_sellers(
|
async fn list_sellers(
|
||||||
rendezvous_point: Multiaddr,
|
rendezvous_point: Multiaddr,
|
||||||
context: &Arc<Context>,
|
context: &Arc<Context>,
|
||||||
) -> anyhow::Result<serde_json::Value, Error> {
|
) -> Result<serde_json::Value, jsonrpsee_core::Error> {
|
||||||
let request = Request {
|
let request = Request {
|
||||||
params: Params {
|
params: Params {
|
||||||
rendezvous_point: Some(rendezvous_point),
|
rendezvous_point: Some(rendezvous_point),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
cmd: Command::ListSellers,
|
cmd: Method::ListSellers,
|
||||||
};
|
};
|
||||||
let result = request.call(Arc::clone(context)).await.unwrap();
|
let result = request.call(Arc::clone(context))
|
||||||
|
.await
|
||||||
|
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user