mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-04-21 00:06:38 -04:00
refactor(swap, tauri_bindings): Overhaul API architecture and introduce Tauri events
- Implement trait-based request handling in api/request.rs - Add Tauri bindings and event system in api/tauri_bindings.rs - Refactor CLI command parsing and execution in cli/command.rs - Update RPC methods to use new request handling approach - Emit Tauri events in swap/src/protocol/bob/swap.rs - Add typescript type bindings use typeshare crate
This commit is contained in:
parent
fea1e66c64
commit
4939d63524
@ -51,6 +51,7 @@ libp2p = { version = "0.42.2", default-features = false, features = [
|
||||
"ping",
|
||||
"rendezvous",
|
||||
"identify",
|
||||
"serde",
|
||||
] }
|
||||
monero = { version = "0.12", features = [ "serde_support" ] }
|
||||
monero-rpc = { path = "../monero-rpc" }
|
||||
@ -67,7 +68,7 @@ reqwest = { version = "0.12", features = [
|
||||
], default-features = false }
|
||||
rust_decimal = { version = "1", features = [ "serde-float" ] }
|
||||
rust_decimal_macros = "1"
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
serde_cbor = "0.11"
|
||||
serde_json = "1"
|
||||
serde_with = { version = "1", features = [ "macros" ] }
|
||||
@ -85,6 +86,7 @@ sqlx = { version = "0.6.3", features = [
|
||||
] }
|
||||
structopt = "0.3"
|
||||
strum = { version = "0.26", features = [ "derive" ] }
|
||||
tauri = { version = "2.0.0-rc.1", features = [ "config-json5" ] }
|
||||
thiserror = "1"
|
||||
time = "0.3"
|
||||
tokio = { version = "1", features = [
|
||||
@ -118,6 +120,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [
|
||||
"tracing-log",
|
||||
"json",
|
||||
] }
|
||||
typeshare = "1.0.3"
|
||||
url = { version = "2", features = [ "serde" ] }
|
||||
uuid = { version = "1.9", features = [ "serde", "v4" ] }
|
||||
void = "1"
|
||||
|
@ -1,4 +1,5 @@
|
||||
pub mod request;
|
||||
pub mod tauri_bindings;
|
||||
use crate::cli::command::{Bitcoin, Monero, Tor};
|
||||
use crate::database::open_db;
|
||||
use crate::env::{Config as EnvConfig, GetConfig, Mainnet, Testnet};
|
||||
@ -13,10 +14,13 @@ use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex, Once};
|
||||
use std::sync::{Arc, Mutex as SyncMutex, Once};
|
||||
use tauri::AppHandle;
|
||||
use tauri_bindings::TauriHandle;
|
||||
use tokio::sync::{broadcast, broadcast::Sender, Mutex as TokioMutex, RwLock};
|
||||
use tokio::task::JoinHandle;
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
static START: Once = Once::new();
|
||||
|
||||
@ -33,8 +37,6 @@ pub struct Config {
|
||||
is_testnet: bool,
|
||||
}
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PendingTaskList(TokioMutex<Vec<JoinHandle<()>>>);
|
||||
|
||||
@ -64,6 +66,13 @@ impl PendingTaskList {
|
||||
}
|
||||
}
|
||||
|
||||
/// The `SwapLock` manages the state of the current swap, ensuring that only one swap can be active at a time.
|
||||
/// It includes:
|
||||
/// - A lock for the current swap (`current_swap`)
|
||||
/// - A broadcast channel for suspension signals (`suspension_trigger`)
|
||||
///
|
||||
/// The `SwapLock` provides methods to acquire and release the swap lock, and to listen for suspension signals.
|
||||
/// This ensures that swap operations do not overlap and can be safely suspended if needed.
|
||||
pub struct SwapLock {
|
||||
current_swap: RwLock<Option<Uuid>>,
|
||||
suspension_trigger: Sender<()>,
|
||||
@ -157,17 +166,22 @@ impl Default for SwapLock {
|
||||
}
|
||||
}
|
||||
|
||||
// workaround for warning over monero_rpc_process which we must own but not read
|
||||
#[allow(dead_code)]
|
||||
/// Holds shared data for different parts of the CLI.
|
||||
///
|
||||
/// Some components are optional, allowing initialization of only necessary parts.
|
||||
/// For example, the `history` command doesn't require wallet initialization.
|
||||
///
|
||||
/// Many fields are wrapped in `Arc` for thread-safe sharing.
|
||||
#[derive(Clone)]
|
||||
pub struct Context {
|
||||
pub db: Arc<dyn Database + Send + Sync>,
|
||||
bitcoin_wallet: Option<Arc<bitcoin::Wallet>>,
|
||||
monero_wallet: Option<Arc<monero::Wallet>>,
|
||||
monero_rpc_process: Option<Arc<Mutex<monero::WalletRpcProcess>>>,
|
||||
pub swap_lock: Arc<SwapLock>,
|
||||
pub config: Config,
|
||||
pub tasks: Arc<PendingTaskList>,
|
||||
tauri_handle: Option<TauriHandle>,
|
||||
bitcoin_wallet: Option<Arc<bitcoin::Wallet>>,
|
||||
monero_wallet: Option<Arc<monero::Wallet>>,
|
||||
monero_rpc_process: Option<Arc<SyncMutex<monero::WalletRpcProcess>>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -216,7 +230,7 @@ impl Context {
|
||||
let monero_daemon_address = monero.apply_defaults(is_testnet);
|
||||
let (wlt, prc) =
|
||||
init_monero_wallet(data_dir.clone(), monero_daemon_address, env_config).await?;
|
||||
(Some(Arc::new(wlt)), Some(prc))
|
||||
(Some(Arc::new(wlt)), Some(Arc::new(SyncMutex::new(prc))))
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
@ -228,7 +242,7 @@ impl Context {
|
||||
db: open_db(data_dir.join("sqlite")).await?,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
monero_rpc_process: monero_rpc_process.map(|prc| Arc::new(Mutex::new(prc))),
|
||||
monero_rpc_process,
|
||||
config: Config {
|
||||
tor_socks5_port,
|
||||
namespace: XmrBtcNamespace::from_is_testnet(is_testnet),
|
||||
@ -242,11 +256,18 @@ impl Context {
|
||||
},
|
||||
swap_lock: Arc::new(SwapLock::new()),
|
||||
tasks: Arc::new(PendingTaskList::default()),
|
||||
tauri_handle: None,
|
||||
};
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
pub fn with_tauri_handle(mut self, tauri_handle: AppHandle) -> Self {
|
||||
self.tauri_handle = Some(TauriHandle::new(tauri_handle));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn for_harness(
|
||||
seed: Seed,
|
||||
env_config: EnvConfig,
|
||||
@ -266,6 +287,7 @@ impl Context {
|
||||
monero_rpc_process: None,
|
||||
swap_lock: Arc::new(SwapLock::new()),
|
||||
tasks: Arc::new(PendingTaskList::default()),
|
||||
tauri_handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,12 +408,6 @@ impl Config {
|
||||
#[cfg(test)]
|
||||
pub mod api_test {
|
||||
use super::*;
|
||||
use crate::api::request::{Method, Request};
|
||||
|
||||
use libp2p::Multiaddr;
|
||||
use request::BuyXmrArgs;
|
||||
use std::str::FromStr;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub const MULTI_ADDRESS: &str =
|
||||
"/ip4/127.0.0.1/tcp/9939/p2p/12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi";
|
||||
@ -426,50 +442,4 @@ pub mod api_test {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn buy_xmr(is_testnet: bool) -> Request {
|
||||
let seller = Multiaddr::from_str(MULTI_ADDRESS).unwrap();
|
||||
let bitcoin_change_address = {
|
||||
if is_testnet {
|
||||
bitcoin::Address::from_str(BITCOIN_TESTNET_ADDRESS).unwrap()
|
||||
} else {
|
||||
bitcoin::Address::from_str(BITCOIN_MAINNET_ADDRESS).unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
let monero_receive_address = {
|
||||
if is_testnet {
|
||||
monero::Address::from_str(MONERO_STAGENET_ADDRESS).unwrap()
|
||||
} else {
|
||||
monero::Address::from_str(MONERO_MAINNET_ADDRESS).unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
Request::new(Method::BuyXmr(BuyXmrArgs {
|
||||
seller,
|
||||
bitcoin_change_address,
|
||||
monero_receive_address,
|
||||
swap_id: Uuid::new_v4(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn resume() -> Request {
|
||||
Request::new(Method::Resume {
|
||||
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cancel() -> Request {
|
||||
Request::new(Method::CancelAndRefund {
|
||||
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn refund() -> Request {
|
||||
Request::new(Method::CancelAndRefund {
|
||||
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
use super::tauri_bindings::TauriHandle;
|
||||
use crate::api::tauri_bindings::{TauriEmitter, TauriSwapProgressEvent};
|
||||
use crate::api::Context;
|
||||
use crate::bitcoin::{Amount, ExpiredTimelocks, TxLock};
|
||||
use crate::bitcoin::{CancelTimelock, ExpiredTimelocks, PunishTimelock, TxLock};
|
||||
use crate::cli::{list_sellers as list_sellers_impl, EventLoop, SellerStatus};
|
||||
use crate::libp2p_ext::MultiAddrExt;
|
||||
use crate::network::quote::{BidQuote, ZeroQuoteReceived};
|
||||
@ -10,11 +12,11 @@ use crate::{bitcoin, cli, monero, rpc};
|
||||
use ::bitcoin::Txid;
|
||||
use anyhow::{bail, Context as AnyContext, Result};
|
||||
use libp2p::core::Multiaddr;
|
||||
use libp2p::PeerId;
|
||||
use qrcode::render::unicode;
|
||||
use qrcode::QrCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::cmp::min;
|
||||
use std::convert::TryInto;
|
||||
use std::future::Future;
|
||||
@ -22,144 +24,334 @@ use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tracing::Instrument;
|
||||
use typeshare::typeshare;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Request {
|
||||
pub cmd: Method,
|
||||
pub log_reference: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct BuyXmrArgs {
|
||||
pub seller: Multiaddr,
|
||||
pub bitcoin_change_address: bitcoin::Address,
|
||||
pub monero_receive_address: monero::Address,
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct ResumeArgs {
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct CancelAndRefundArgs {
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct MoneroRecoveryArgs {
|
||||
pub swap_id: Uuid,
|
||||
/// This trait is implemented by all types of request args that
|
||||
/// the CLI can handle.
|
||||
/// It provides a unified abstraction that can be useful for generics.
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait Request {
|
||||
type Response: Serialize;
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response>;
|
||||
}
|
||||
|
||||
// BuyXmr
|
||||
#[typeshare]
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct WithdrawBtcArgs {
|
||||
pub amount: Option<u64>,
|
||||
pub address: bitcoin::Address,
|
||||
pub struct BuyXmrArgs {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub seller: Multiaddr,
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub bitcoin_change_address: bitcoin::Address,
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub monero_receive_address: monero::Address,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct BalanceArgs {
|
||||
pub force_refresh: bool,
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct BuyXmrResponse {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub swap_id: Uuid,
|
||||
pub quote: BidQuote,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct ListSellersArgs {
|
||||
pub rendezvous_point: Multiaddr,
|
||||
impl Request for BuyXmrArgs {
|
||||
type Response = BuyXmrResponse;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
buy_xmr(self, ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct StartDaemonArgs {
|
||||
pub server_address: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct GetSwapInfoArgs {
|
||||
// ResumeSwap
|
||||
#[typeshare]
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ResumeSwapArgs {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ResumeSwapResponse {
|
||||
pub result: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct BalanceResponse {
|
||||
pub balance: u64, // in satoshis
|
||||
impl Request for ResumeSwapArgs {
|
||||
type Response = ResumeSwapResponse;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
resume_swap(self, ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct BuyXmrResponse {
|
||||
pub swap_id: String,
|
||||
pub quote: BidQuote, // You'll need to import or define BidQuote
|
||||
// CancelAndRefund
|
||||
#[typeshare]
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CancelAndRefundArgs {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetHistoryResponse {
|
||||
swaps: Vec<(Uuid, String)>,
|
||||
impl Request for CancelAndRefundArgs {
|
||||
type Response = serde_json::Value;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
cancel_and_refund(self, ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
// MoneroRecovery
|
||||
#[typeshare]
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MoneroRecoveryArgs {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
impl Request for MoneroRecoveryArgs {
|
||||
type Response = serde_json::Value;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
monero_recovery(self, ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
// WithdrawBtc
|
||||
#[typeshare]
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct WithdrawBtcArgs {
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(default, with = "::bitcoin::util::amount::serde::as_sat::opt")]
|
||||
pub amount: Option<bitcoin::Amount>,
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub address: bitcoin::Address,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct WithdrawBtcResponse {
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
pub amount: bitcoin::Amount,
|
||||
pub txid: String,
|
||||
}
|
||||
|
||||
impl Request for WithdrawBtcArgs {
|
||||
type Response = WithdrawBtcResponse;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
withdraw_btc(self, ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
// ListSellers
|
||||
#[typeshare]
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ListSellersArgs {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub rendezvous_point: Multiaddr,
|
||||
}
|
||||
|
||||
impl Request for ListSellersArgs {
|
||||
type Response = serde_json::Value;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
list_sellers(self, ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
// StartDaemon
|
||||
#[typeshare]
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct StartDaemonArgs {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub server_address: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
impl Request for StartDaemonArgs {
|
||||
type Response = serde_json::Value;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
start_daemon(self, (*ctx).clone()).await
|
||||
}
|
||||
}
|
||||
|
||||
// GetSwapInfo
|
||||
#[typeshare]
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct GetSwapInfoArgs {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize)]
|
||||
pub struct GetSwapInfoResponse {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub swap_id: Uuid,
|
||||
pub seller: Seller,
|
||||
pub completed: bool,
|
||||
pub start_date: String,
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub state_name: String,
|
||||
pub xmr_amount: u64,
|
||||
pub btc_amount: u64,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
pub xmr_amount: monero::Amount,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
pub btc_amount: bitcoin::Amount,
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub tx_lock_id: Txid,
|
||||
pub tx_cancel_fee: u64,
|
||||
pub tx_refund_fee: u64,
|
||||
pub tx_lock_fee: u64,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
pub tx_cancel_fee: bitcoin::Amount,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
pub tx_refund_fee: bitcoin::Amount,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
pub tx_lock_fee: bitcoin::Amount,
|
||||
pub btc_refund_address: String,
|
||||
pub cancel_timelock: u32,
|
||||
pub punish_timelock: u32,
|
||||
pub cancel_timelock: CancelTimelock,
|
||||
pub punish_timelock: PunishTimelock,
|
||||
pub timelock: Option<ExpiredTimelocks>,
|
||||
}
|
||||
|
||||
impl Request for GetSwapInfoArgs {
|
||||
type Response = GetSwapInfoResponse;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
get_swap_info(self, ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
// Balance
|
||||
#[typeshare]
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BalanceArgs {
|
||||
pub force_refresh: bool,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct WithdrawBtcResponse {
|
||||
amount: u64,
|
||||
txid: String,
|
||||
pub struct BalanceResponse {
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
pub balance: bitcoin::Amount,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
impl Request for BalanceArgs {
|
||||
type Response = BalanceResponse;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
get_balance(self, ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
// GetHistory
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetHistoryArgs;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GetHistoryEntry {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
swap_id: Uuid,
|
||||
state: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetHistoryResponse {
|
||||
pub swaps: Vec<GetHistoryEntry>,
|
||||
}
|
||||
|
||||
impl Request for GetHistoryArgs {
|
||||
type Response = GetHistoryResponse;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
get_history(ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
// Additional structs
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Seller {
|
||||
pub peer_id: String,
|
||||
pub addresses: Vec<Multiaddr>,
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub peer_id: PeerId,
|
||||
pub addresses: Vec<String>,
|
||||
}
|
||||
|
||||
// TODO: We probably dont even need this.
|
||||
// We can just call the method directly from the RPC server, the CLI and the Tauri connector
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Method {
|
||||
BuyXmr(BuyXmrArgs),
|
||||
Resume(ResumeArgs),
|
||||
CancelAndRefund(CancelAndRefundArgs),
|
||||
MoneroRecovery(MoneroRecoveryArgs),
|
||||
History,
|
||||
Config,
|
||||
WithdrawBtc(WithdrawBtcArgs),
|
||||
Balance(BalanceArgs),
|
||||
ListSellers(ListSellersArgs),
|
||||
ExportBitcoinWallet,
|
||||
SuspendCurrentSwap,
|
||||
StartDaemon(StartDaemonArgs),
|
||||
GetCurrentSwap,
|
||||
GetSwapInfo(GetSwapInfoArgs),
|
||||
GetRawStates,
|
||||
// Suspend current swap
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SuspendCurrentSwapArgs;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SuspendCurrentSwapResponse {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
impl Request for SuspendCurrentSwapArgs {
|
||||
type Response = SuspendCurrentSwapResponse;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
suspend_current_swap(ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GetCurrentSwap;
|
||||
|
||||
impl Request for GetCurrentSwap {
|
||||
type Response = serde_json::Value;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
get_current_swap(ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GetConfig;
|
||||
|
||||
impl Request for GetConfig {
|
||||
type Response = serde_json::Value;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
get_config(ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExportBitcoinWalletArgs;
|
||||
|
||||
impl Request for ExportBitcoinWalletArgs {
|
||||
type Response = serde_json::Value;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
export_bitcoin_wallet(ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GetConfigArgs;
|
||||
|
||||
impl Request for GetConfigArgs {
|
||||
type Response = serde_json::Value;
|
||||
|
||||
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||
get_config(ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(fields(method = "suspend_current_swap"), skip(context))]
|
||||
pub async fn suspend_current_swap(context: Arc<Context>) -> Result<serde_json::Value> {
|
||||
pub async fn suspend_current_swap(context: Arc<Context>) -> Result<SuspendCurrentSwapResponse> {
|
||||
let swap_id = context.swap_lock.get_current_swap_id().await;
|
||||
|
||||
if let Some(id_value) = swap_id {
|
||||
context.swap_lock.send_suspend_signal().await?;
|
||||
|
||||
Ok(json!({ "swapId": id_value }))
|
||||
Ok(SuspendCurrentSwapResponse { swap_id: id_value })
|
||||
} else {
|
||||
bail!("No swap is currently running")
|
||||
}
|
||||
@ -191,7 +383,7 @@ pub async fn get_swap_info(
|
||||
let state = context.db.get_state(args.swap_id).await?;
|
||||
let is_completed = state.swap_finished();
|
||||
|
||||
let peerId = context
|
||||
let peer_id = context
|
||||
.db
|
||||
.get_peer_id(args.swap_id)
|
||||
.await
|
||||
@ -199,14 +391,13 @@ pub async fn get_swap_info(
|
||||
|
||||
let addresses = context
|
||||
.db
|
||||
.get_addresses(peerId)
|
||||
.get_addresses(peer_id)
|
||||
.await
|
||||
.with_context(|| "Could not get addressess")?;
|
||||
|
||||
let start_date = context.db.get_swap_start_date(args.swap_id).await?;
|
||||
|
||||
let swap_state: BobState = state.try_into()?;
|
||||
let state_name = format!("{}", swap_state);
|
||||
|
||||
let (
|
||||
xmr_amount,
|
||||
@ -226,15 +417,13 @@ pub async fn get_swap_info(
|
||||
.find_map(|state| {
|
||||
if let State::Bob(BobState::SwapSetupCompleted(state2)) = state {
|
||||
let xmr_amount = state2.xmr;
|
||||
let btc_amount = state2.tx_lock.lock_amount().to_sat();
|
||||
let tx_cancel_fee = state2.tx_cancel_fee.to_sat();
|
||||
let tx_refund_fee = state2.tx_refund_fee.to_sat();
|
||||
let btc_amount = state2.tx_lock.lock_amount();
|
||||
let tx_cancel_fee = state2.tx_cancel_fee;
|
||||
let tx_refund_fee = state2.tx_refund_fee;
|
||||
let tx_lock_id = state2.tx_lock.txid();
|
||||
let btc_refund_address = state2.refund_address.to_string();
|
||||
|
||||
if let Ok(tx_lock_fee) = state2.tx_lock.fee() {
|
||||
let tx_lock_fee = tx_lock_fee.to_sat();
|
||||
|
||||
Some((
|
||||
xmr_amount,
|
||||
btc_amount,
|
||||
@ -255,7 +444,7 @@ pub async fn get_swap_info(
|
||||
})
|
||||
.with_context(|| "Did not find SwapSetupCompleted state for swap")?;
|
||||
|
||||
let timelock = match swap_state {
|
||||
let timelock = match swap_state.clone() {
|
||||
BobState::Started { .. } | BobState::SafelyAborted | BobState::SwapSetupCompleted(_) => {
|
||||
None
|
||||
}
|
||||
@ -276,21 +465,21 @@ pub async fn get_swap_info(
|
||||
Ok(GetSwapInfoResponse {
|
||||
swap_id: args.swap_id,
|
||||
seller: Seller {
|
||||
peer_id: peerId.to_string(),
|
||||
addresses,
|
||||
peer_id,
|
||||
addresses: addresses.iter().map(|a| a.to_string()).collect(),
|
||||
},
|
||||
completed: is_completed,
|
||||
start_date,
|
||||
state_name,
|
||||
xmr_amount: xmr_amount.as_piconero(),
|
||||
state_name: format!("{}", swap_state),
|
||||
xmr_amount,
|
||||
btc_amount,
|
||||
tx_lock_id,
|
||||
tx_cancel_fee,
|
||||
tx_refund_fee,
|
||||
tx_lock_fee,
|
||||
btc_refund_address: btc_refund_address.to_string(),
|
||||
cancel_timelock: cancel_timelock.into(),
|
||||
punish_timelock: punish_timelock.into(),
|
||||
cancel_timelock,
|
||||
punish_timelock,
|
||||
timelock,
|
||||
})
|
||||
}
|
||||
@ -299,13 +488,15 @@ pub async fn get_swap_info(
|
||||
pub async fn buy_xmr(
|
||||
buy_xmr: BuyXmrArgs,
|
||||
context: Arc<Context>,
|
||||
) -> Result<serde_json::Value, anyhow::Error> {
|
||||
) -> Result<BuyXmrResponse, anyhow::Error> {
|
||||
let BuyXmrArgs {
|
||||
seller,
|
||||
bitcoin_change_address,
|
||||
monero_receive_address,
|
||||
swap_id,
|
||||
} = buy_xmr;
|
||||
|
||||
let swap_id = Uuid::new_v4();
|
||||
|
||||
let bitcoin_wallet = Arc::clone(
|
||||
context
|
||||
.bitcoin_wallet
|
||||
@ -358,6 +549,9 @@ pub async fn buy_xmr(
|
||||
_ = context.swap_lock.listen_for_swap_force_suspension() => {
|
||||
tracing::debug!("Shutdown signal received, exiting");
|
||||
context.swap_lock.release_swap_lock().await.expect("Shutdown signal received but failed to release swap lock. The swap process has been terminated but the swap lock is still active.");
|
||||
|
||||
context.tauri_handle.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::Released);
|
||||
|
||||
bail!("Shutdown signal received");
|
||||
},
|
||||
result = async {
|
||||
@ -382,16 +576,28 @@ pub async fn buy_xmr(
|
||||
.release_swap_lock()
|
||||
.await
|
||||
.expect("Could not release swap lock");
|
||||
|
||||
context
|
||||
.tauri_handle
|
||||
.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::Released);
|
||||
|
||||
bail!(error);
|
||||
}
|
||||
};
|
||||
|
||||
context
|
||||
.tauri_handle
|
||||
.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::ReceivedQuote(bid_quote));
|
||||
|
||||
context.tasks.clone().spawn(async move {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = context.swap_lock.listen_for_swap_force_suspension() => {
|
||||
tracing::debug!("Shutdown signal received, exiting");
|
||||
context.swap_lock.release_swap_lock().await.expect("Shutdown signal received but failed to release swap lock. The swap process has been terminated but the swap lock is still active.");
|
||||
|
||||
context.tauri_handle.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::Released);
|
||||
|
||||
bail!("Shutdown signal received");
|
||||
},
|
||||
event_loop_result = event_loop => {
|
||||
@ -416,6 +622,7 @@ pub async fn buy_xmr(
|
||||
max_givable,
|
||||
|| bitcoin_wallet.sync(),
|
||||
estimate_fee,
|
||||
context.tauri_handle.clone(),
|
||||
);
|
||||
|
||||
let (amount, fees) = match determine_amount.await {
|
||||
@ -442,7 +649,7 @@ pub async fn buy_xmr(
|
||||
monero_receive_address,
|
||||
bitcoin_change_address,
|
||||
amount,
|
||||
);
|
||||
).with_event_emitter(context.tauri_handle.clone());
|
||||
|
||||
bob::run(swap).await
|
||||
} => {
|
||||
@ -463,18 +670,24 @@ pub async fn buy_xmr(
|
||||
.release_swap_lock()
|
||||
.await
|
||||
.expect("Could not release swap lock");
|
||||
|
||||
context.tauri_handle.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::Released);
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}.in_current_span()).await;
|
||||
|
||||
Ok(json!({
|
||||
"swapId": swap_id.to_string(),
|
||||
"quote": bid_quote,
|
||||
}))
|
||||
Ok(BuyXmrResponse {
|
||||
swap_id,
|
||||
quote: bid_quote,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(fields(method = "resume_swap"), skip(context))]
|
||||
pub async fn resume_swap(resume: ResumeArgs, context: Arc<Context>) -> Result<serde_json::Value> {
|
||||
let ResumeArgs { swap_id } = resume;
|
||||
pub async fn resume_swap(
|
||||
resume: ResumeSwapArgs,
|
||||
context: Arc<Context>,
|
||||
) -> Result<ResumeSwapResponse> {
|
||||
let ResumeSwapArgs { swap_id } = resume;
|
||||
context.swap_lock.acquire_swap_lock(swap_id).await?;
|
||||
|
||||
let seller_peer_id = context.db.get_peer_id(swap_id).await?;
|
||||
@ -531,7 +744,8 @@ pub async fn resume_swap(resume: ResumeArgs, context: Arc<Context>) -> Result<se
|
||||
event_loop_handle,
|
||||
monero_receive_address,
|
||||
)
|
||||
.await?;
|
||||
.await?
|
||||
.with_event_emitter(context.tauri_handle.clone());
|
||||
|
||||
context.tasks.clone().spawn(
|
||||
async move {
|
||||
@ -541,6 +755,9 @@ pub async fn resume_swap(resume: ResumeArgs, context: Arc<Context>) -> Result<se
|
||||
_ = context.swap_lock.listen_for_swap_force_suspension() => {
|
||||
tracing::debug!("Shutdown signal received, exiting");
|
||||
context.swap_lock.release_swap_lock().await.expect("Shutdown signal received but failed to release swap lock. The swap process has been terminated but the swap lock is still active.");
|
||||
|
||||
context.tauri_handle.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::Released);
|
||||
|
||||
bail!("Shutdown signal received");
|
||||
},
|
||||
|
||||
@ -571,13 +788,17 @@ pub async fn resume_swap(resume: ResumeArgs, context: Arc<Context>) -> Result<se
|
||||
.release_swap_lock()
|
||||
.await
|
||||
.expect("Could not release swap lock");
|
||||
|
||||
context.tauri_handle.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::Released);
|
||||
|
||||
Ok::<(), anyhow::Error>(())
|
||||
}
|
||||
.in_current_span(),
|
||||
).await;
|
||||
Ok(json!({
|
||||
"result": "ok",
|
||||
}))
|
||||
|
||||
Ok(ResumeSwapResponse {
|
||||
result: "OK".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(fields(method = "cancel_and_refund"), skip(context))]
|
||||
@ -602,6 +823,10 @@ pub async fn cancel_and_refund(
|
||||
.await
|
||||
.expect("Could not release swap lock");
|
||||
|
||||
context
|
||||
.tauri_handle
|
||||
.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::Released);
|
||||
|
||||
state.map(|state| {
|
||||
json!({
|
||||
"result": state,
|
||||
@ -612,10 +837,13 @@ pub async fn cancel_and_refund(
|
||||
#[tracing::instrument(fields(method = "get_history"), skip(context))]
|
||||
pub async fn get_history(context: Arc<Context>) -> Result<GetHistoryResponse> {
|
||||
let swaps = context.db.all().await?;
|
||||
let mut vec: Vec<(Uuid, String)> = Vec::new();
|
||||
let mut vec: Vec<GetHistoryEntry> = Vec::new();
|
||||
for (swap_id, state) in swaps {
|
||||
let state: BobState = state.try_into()?;
|
||||
vec.push((swap_id, state.to_string()));
|
||||
vec.push(GetHistoryEntry {
|
||||
swap_id,
|
||||
state: state.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(GetHistoryResponse { swaps: vec })
|
||||
@ -659,7 +887,7 @@ pub async fn withdraw_btc(
|
||||
.context("Could not get Bitcoin wallet")?;
|
||||
|
||||
let amount = match amount {
|
||||
Some(amount) => Amount::from_sat(amount),
|
||||
Some(amount) => amount,
|
||||
None => {
|
||||
bitcoin_wallet
|
||||
.max_giveable(address.script_pubkey().len())
|
||||
@ -677,7 +905,7 @@ pub async fn withdraw_btc(
|
||||
|
||||
Ok(WithdrawBtcResponse {
|
||||
txid: signed_tx.txid().to_string(),
|
||||
amount: amount.to_sat(),
|
||||
amount,
|
||||
})
|
||||
}
|
||||
|
||||
@ -728,7 +956,7 @@ pub async fn get_balance(balance: BalanceArgs, context: Arc<Context>) -> Result<
|
||||
}
|
||||
|
||||
Ok(BalanceResponse {
|
||||
balance: bitcoin_balance.to_sat(),
|
||||
balance: bitcoin_balance,
|
||||
})
|
||||
}
|
||||
|
||||
@ -838,26 +1066,6 @@ pub async fn get_current_swap(context: Arc<Context>) -> Result<serde_json::Value
|
||||
}))
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn new(cmd: Method) -> Request {
|
||||
Request {
|
||||
cmd,
|
||||
log_reference: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_id(cmd: Method, id: Option<String>) -> Request {
|
||||
Request {
|
||||
cmd,
|
||||
log_reference: id,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call(self, _: Arc<Context>) -> Result<JsonValue> {
|
||||
unreachable!("This function should never be called")
|
||||
}
|
||||
}
|
||||
|
||||
fn qr_code(value: &impl ToString) -> Result<String> {
|
||||
let code = QrCode::new(value.to_string())?;
|
||||
let qr_code = code
|
||||
@ -868,6 +1076,7 @@ fn qr_code(value: &impl ToString) -> Result<String> {
|
||||
Ok(qr_code)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS, FFE, TFE>(
|
||||
json: bool,
|
||||
bid_quote: BidQuote,
|
||||
@ -876,18 +1085,19 @@ pub async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS, FFE, TFE>(
|
||||
max_giveable_fn: FMG,
|
||||
sync: FS,
|
||||
estimate_fee: FFE,
|
||||
) -> Result<(Amount, Amount)>
|
||||
event_emitter: Option<TauriHandle>,
|
||||
) -> Result<(bitcoin::Amount, bitcoin::Amount)>
|
||||
where
|
||||
TB: Future<Output = Result<Amount>>,
|
||||
TB: Future<Output = Result<bitcoin::Amount>>,
|
||||
FB: Fn() -> TB,
|
||||
TMG: Future<Output = Result<Amount>>,
|
||||
TMG: Future<Output = Result<bitcoin::Amount>>,
|
||||
FMG: Fn() -> TMG,
|
||||
TS: Future<Output = Result<()>>,
|
||||
FS: Fn() -> TS,
|
||||
FFE: Fn(Amount) -> TFE,
|
||||
TFE: Future<Output = Result<Amount>>,
|
||||
FFE: Fn(bitcoin::Amount) -> TFE,
|
||||
TFE: Future<Output = Result<bitcoin::Amount>>,
|
||||
{
|
||||
if bid_quote.max_quantity == Amount::ZERO {
|
||||
if bid_quote.max_quantity == bitcoin::Amount::ZERO {
|
||||
bail!(ZeroQuoteReceived)
|
||||
}
|
||||
|
||||
@ -901,7 +1111,7 @@ where
|
||||
sync().await?;
|
||||
let mut max_giveable = max_giveable_fn().await?;
|
||||
|
||||
if max_giveable == Amount::ZERO || max_giveable < bid_quote.min_quantity {
|
||||
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;
|
||||
@ -933,6 +1143,19 @@ where
|
||||
"Waiting for Bitcoin deposit",
|
||||
);
|
||||
|
||||
// TODO: Use the real swap id here
|
||||
event_emitter.emit_swap_progress_event(
|
||||
Uuid::new_v4(),
|
||||
TauriSwapProgressEvent::WaitingForBtcDeposit {
|
||||
deposit_address: deposit_address.clone(),
|
||||
max_giveable,
|
||||
min_deposit_until_swap_will_start,
|
||||
max_deposit_until_maximum_amount_is_reached,
|
||||
min_bitcoin_lock_tx_fee,
|
||||
quote: bid_quote.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
max_giveable = loop {
|
||||
sync().await?;
|
||||
let new_max_givable = max_giveable_fn().await?;
|
||||
|
141
swap/src/api/tauri_bindings.rs
Normal file
141
swap/src/api/tauri_bindings.rs
Normal file
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* TOOD: Perhaps we should move this to the `src-tauri` package.
|
||||
*/
|
||||
use anyhow::Result;
|
||||
use bitcoin::Txid;
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use typeshare::typeshare;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{monero, network::quote::BidQuote};
|
||||
|
||||
static SWAP_PROGRESS_EVENT_NAME: &str = "swap-progress-update";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TauriHandle(Arc<AppHandle>);
|
||||
|
||||
impl TauriHandle {
|
||||
pub fn new(tauri_handle: AppHandle) -> Self {
|
||||
Self(Arc::new(tauri_handle))
|
||||
}
|
||||
|
||||
pub fn emit_tauri_event<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
|
||||
self.0.emit(event, payload).map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TauriEmitter {
|
||||
fn emit_tauri_event<S: Serialize + Clone>(
|
||||
&self,
|
||||
event: &str,
|
||||
payload: S,
|
||||
) -> Result<()>;
|
||||
|
||||
fn emit_swap_progress_event(&self, swap_id: Uuid, event: TauriSwapProgressEvent) {
|
||||
let _ = self.emit_tauri_event(
|
||||
SWAP_PROGRESS_EVENT_NAME,
|
||||
TauriSwapProgressEventWrapper { swap_id, event },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl TauriEmitter for TauriHandle {
|
||||
fn emit_tauri_event<S: Serialize + Clone>(
|
||||
&self,
|
||||
event: &str,
|
||||
payload: S,
|
||||
) -> Result<()> {
|
||||
self.emit_tauri_event(event, payload)
|
||||
}
|
||||
}
|
||||
|
||||
impl TauriEmitter for Option<TauriHandle> {
|
||||
fn emit_tauri_event<S: Serialize + Clone>(
|
||||
&self,
|
||||
event: &str,
|
||||
payload: S,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
Some(tauri) => tauri.emit_tauri_event(event, payload),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[typeshare]
|
||||
pub struct TauriSwapProgressEventWrapper {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
swap_id: Uuid,
|
||||
event: TauriSwapProgressEvent,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
#[typeshare]
|
||||
pub enum TauriSwapProgressEvent {
|
||||
Initiated,
|
||||
ReceivedQuote(BidQuote),
|
||||
WaitingForBtcDeposit {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
deposit_address: bitcoin::Address,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
max_giveable: bitcoin::Amount,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
min_deposit_until_swap_will_start: bitcoin::Amount,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
max_deposit_until_maximum_amount_is_reached: bitcoin::Amount,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
min_bitcoin_lock_tx_fee: bitcoin::Amount,
|
||||
quote: BidQuote,
|
||||
},
|
||||
Started {
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
btc_lock_amount: bitcoin::Amount,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
btc_tx_lock_fee: bitcoin::Amount,
|
||||
},
|
||||
BtcLockTxInMempool {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
btc_lock_txid: bitcoin::Txid,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
btc_lock_confirmations: u64,
|
||||
},
|
||||
XmrLockTxInMempool {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
xmr_lock_txid: monero::TxHash,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
xmr_lock_tx_confirmations: u64,
|
||||
},
|
||||
XmrLocked,
|
||||
BtcRedeemed,
|
||||
XmrRedeemInMempool {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
xmr_redeem_txid: monero::TxHash,
|
||||
#[typeshare(serialized_as = "string")]
|
||||
xmr_redeem_address: monero::Address,
|
||||
},
|
||||
BtcCancelled {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
btc_cancel_txid: Txid,
|
||||
},
|
||||
BtcRefunded {
|
||||
#[typeshare(serialized_as = "string")]
|
||||
btc_refund_txid: Txid,
|
||||
},
|
||||
BtcPunished,
|
||||
AttemptingCooperativeRedeem,
|
||||
CooperativeRedeemAccepted,
|
||||
CooperativeRedeemRejected {
|
||||
reason: String,
|
||||
},
|
||||
Released,
|
||||
}
|
@ -11,10 +11,10 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use swap::cli::command::{parse_args_and_apply_defaults, ParseResult};
|
||||
use swap::common::check_latest_version;
|
||||
use anyhow::Result;
|
||||
use std::env;
|
||||
use swap::cli::command::{parse_args_and_apply_defaults, ParseResult};
|
||||
use swap::common::check_latest_version;
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() -> Result<()> {
|
||||
@ -23,7 +23,9 @@ pub async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
match parse_args_and_apply_defaults(env::args_os()).await? {
|
||||
ParseResult::Success => {}
|
||||
ParseResult::Success(context) => {
|
||||
context.tasks.wait_for_tasks().await?;
|
||||
}
|
||||
ParseResult::PrintAndExitZero { message } => {
|
||||
println!("{}", message);
|
||||
std::process::exit(0);
|
||||
|
@ -16,6 +16,7 @@ use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::ops::Add;
|
||||
use typeshare::typeshare;
|
||||
|
||||
/// Represent a timelock, expressed in relative block height as defined in
|
||||
/// [BIP68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki).
|
||||
@ -23,6 +24,7 @@ use std::ops::Add;
|
||||
/// mined.
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[serde(transparent)]
|
||||
#[typeshare]
|
||||
pub struct CancelTimelock(u32);
|
||||
|
||||
impl From<CancelTimelock> for u32 {
|
||||
@ -69,6 +71,7 @@ impl fmt::Display for CancelTimelock {
|
||||
/// mined.
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[serde(transparent)]
|
||||
#[typeshare]
|
||||
pub struct PunishTimelock(u32);
|
||||
|
||||
impl From<PunishTimelock> for u32 {
|
||||
|
@ -3,6 +3,7 @@ use bdk::electrum_client::HeaderNotification;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::ops::Add;
|
||||
use typeshare::typeshare;
|
||||
|
||||
/// Represent a block height, or block number, expressed in absolute block
|
||||
/// count. E.g. The transaction was included in block #655123, 655123 block
|
||||
@ -37,7 +38,9 @@ impl Add<u32> for BlockHeight {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
pub enum ExpiredTimelocks {
|
||||
None { blocks_left: u32 },
|
||||
Cancel { blocks_left: u32 },
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::api::request::{
|
||||
buy_xmr, cancel_and_refund, export_bitcoin_wallet, get_balance, get_config, get_history,
|
||||
list_sellers, monero_recovery, resume_swap, start_daemon, withdraw_btc, BalanceArgs,
|
||||
BuyXmrArgs, CancelAndRefundArgs, ListSellersArgs, MoneroRecoveryArgs, ResumeArgs,
|
||||
StartDaemonArgs, WithdrawBtcArgs,
|
||||
BuyXmrArgs, CancelAndRefundArgs, ExportBitcoinWalletArgs, GetConfigArgs, GetHistoryArgs,
|
||||
ListSellersArgs, MoneroRecoveryArgs, Request, ResumeSwapArgs, StartDaemonArgs, WithdrawBtcArgs,
|
||||
};
|
||||
use crate::api::Context;
|
||||
use crate::bitcoin::{bitcoin_address, Amount};
|
||||
@ -38,7 +38,7 @@ const DEFAULT_TOR_SOCKS5_PORT: &str = "9050";
|
||||
#[derive(Debug)]
|
||||
pub enum ParseResult {
|
||||
/// The arguments we were invoked in.
|
||||
Success,
|
||||
Success(Arc<Context>),
|
||||
/// A flag or command was given that does not need further processing other
|
||||
/// than printing the provided message.
|
||||
///
|
||||
@ -65,7 +65,7 @@ where
|
||||
let json = args.json;
|
||||
let is_testnet = args.testnet;
|
||||
let data = args.data;
|
||||
let result = match args.cmd {
|
||||
let result: Result<Arc<Context>> = match args.cmd {
|
||||
CliCommand::BuyXmr {
|
||||
seller: Seller { seller },
|
||||
bitcoin,
|
||||
@ -93,36 +93,33 @@ where
|
||||
.await?,
|
||||
);
|
||||
|
||||
buy_xmr(
|
||||
BuyXmrArgs {
|
||||
seller,
|
||||
bitcoin_change_address,
|
||||
monero_receive_address,
|
||||
swap_id: Uuid::new_v4(),
|
||||
},
|
||||
context,
|
||||
)
|
||||
BuyXmrArgs {
|
||||
seller,
|
||||
bitcoin_change_address,
|
||||
monero_receive_address,
|
||||
}
|
||||
.request(context.clone())
|
||||
.await?;
|
||||
|
||||
Ok(()) as Result<(), anyhow::Error>
|
||||
Ok(context)
|
||||
}
|
||||
CliCommand::History => {
|
||||
let context = Arc::new(
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None).await?,
|
||||
);
|
||||
|
||||
get_history(context).await?;
|
||||
GetHistoryArgs {}.request(context.clone()).await?;
|
||||
|
||||
Ok(())
|
||||
Ok(context)
|
||||
}
|
||||
CliCommand::Config => {
|
||||
let context = Arc::new(
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None).await?,
|
||||
);
|
||||
|
||||
get_config(context).await?;
|
||||
GetConfigArgs {}.request(context.clone()).await?;
|
||||
|
||||
Ok(())
|
||||
Ok(context)
|
||||
}
|
||||
CliCommand::Balance { bitcoin } => {
|
||||
let context = Arc::new(
|
||||
@ -139,15 +136,13 @@ where
|
||||
.await?,
|
||||
);
|
||||
|
||||
get_balance(
|
||||
BalanceArgs {
|
||||
force_refresh: true,
|
||||
},
|
||||
context,
|
||||
)
|
||||
BalanceArgs {
|
||||
force_refresh: true,
|
||||
}
|
||||
.request(context.clone())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
Ok(context)
|
||||
}
|
||||
CliCommand::StartDaemon {
|
||||
server_address,
|
||||
@ -155,21 +150,25 @@ where
|
||||
monero,
|
||||
tor,
|
||||
} => {
|
||||
let context = Context::build(
|
||||
Some(bitcoin),
|
||||
Some(monero),
|
||||
Some(tor),
|
||||
data,
|
||||
is_testnet,
|
||||
debug,
|
||||
json,
|
||||
server_address,
|
||||
)
|
||||
.await?;
|
||||
let context = Arc::new(
|
||||
Context::build(
|
||||
Some(bitcoin),
|
||||
Some(monero),
|
||||
Some(tor),
|
||||
data,
|
||||
is_testnet,
|
||||
debug,
|
||||
json,
|
||||
server_address,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
start_daemon(StartDaemonArgs { server_address }, context).await?;
|
||||
StartDaemonArgs { server_address }
|
||||
.request(context.clone())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
Ok(context)
|
||||
}
|
||||
CliCommand::WithdrawBtc {
|
||||
bitcoin,
|
||||
@ -192,16 +191,11 @@ where
|
||||
.await?,
|
||||
);
|
||||
|
||||
withdraw_btc(
|
||||
WithdrawBtcArgs {
|
||||
amount: amount.map(Amount::to_sat),
|
||||
address,
|
||||
},
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
WithdrawBtcArgs { amount, address }
|
||||
.request(context.clone())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
Ok(context)
|
||||
}
|
||||
CliCommand::Resume {
|
||||
swap_id: SwapId { swap_id },
|
||||
@ -223,9 +217,9 @@ where
|
||||
.await?,
|
||||
);
|
||||
|
||||
resume_swap(ResumeArgs { swap_id }, context).await?;
|
||||
ResumeSwapArgs { swap_id }.request(context.clone()).await?;
|
||||
|
||||
Ok(())
|
||||
Ok(context)
|
||||
}
|
||||
CliCommand::CancelAndRefund {
|
||||
swap_id: SwapId { swap_id },
|
||||
@ -246,9 +240,11 @@ where
|
||||
.await?,
|
||||
);
|
||||
|
||||
cancel_and_refund(CancelAndRefundArgs { swap_id }, context).await?;
|
||||
CancelAndRefundArgs { swap_id }
|
||||
.request(context.clone())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
Ok(context)
|
||||
}
|
||||
CliCommand::ListSellers {
|
||||
rendezvous_point,
|
||||
@ -258,9 +254,11 @@ where
|
||||
Context::build(None, None, Some(tor), data, is_testnet, debug, json, None).await?,
|
||||
);
|
||||
|
||||
list_sellers(ListSellersArgs { rendezvous_point }, context).await?;
|
||||
ListSellersArgs { rendezvous_point }
|
||||
.request(context.clone())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
Ok(context)
|
||||
}
|
||||
CliCommand::ExportBitcoinWallet { bitcoin } => {
|
||||
let context = Arc::new(
|
||||
@ -277,9 +275,9 @@ where
|
||||
.await?,
|
||||
);
|
||||
|
||||
export_bitcoin_wallet(context).await?;
|
||||
ExportBitcoinWalletArgs {}.request(context.clone()).await?;
|
||||
|
||||
Ok(())
|
||||
Ok(context)
|
||||
}
|
||||
CliCommand::MoneroRecovery {
|
||||
swap_id: SwapId { swap_id },
|
||||
@ -288,15 +286,15 @@ where
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None).await?,
|
||||
);
|
||||
|
||||
monero_recovery(MoneroRecoveryArgs { swap_id }, context).await?;
|
||||
MoneroRecoveryArgs { swap_id }
|
||||
.request(context.clone())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
Ok(context)
|
||||
}
|
||||
};
|
||||
|
||||
result?;
|
||||
|
||||
Ok(ParseResult::Success)
|
||||
Ok(ParseResult::Success(result?))
|
||||
}
|
||||
|
||||
#[derive(structopt::StructOpt, Debug)]
|
||||
@ -1067,18 +1065,14 @@ mod tests {
|
||||
let args = parse_args_and_apply_defaults(raw_ars).await.unwrap();
|
||||
let (is_testnet, debug, json) = (true, true, false);
|
||||
|
||||
let (expected_config, expected_request) = (
|
||||
Config::default(is_testnet, None, debug, json),
|
||||
Request::resume(),
|
||||
);
|
||||
let expected_config = Config::default(is_testnet, None, debug, json);
|
||||
|
||||
let (actual_config, actual_request) = match args {
|
||||
ParseResult::Context(context, request) => (context.config.clone(), request),
|
||||
let actual_config = match args {
|
||||
ParseResult::Context(context, request) => context.config.clone(),
|
||||
_ => panic!("Couldn't parse result"),
|
||||
};
|
||||
|
||||
assert_eq!(actual_config, expected_config);
|
||||
assert_eq!(actual_request, Box::new(expected_request));
|
||||
|
||||
// given_buy_xmr_on_mainnet_with_json_then_json_set
|
||||
let raw_ars = vec![
|
||||
|
@ -4,6 +4,7 @@ mod wallet_rpc;
|
||||
pub use ::monero::network::Network;
|
||||
pub use ::monero::{Address, PrivateKey, PublicKey};
|
||||
pub use curve25519_dalek::scalar::Scalar;
|
||||
use typeshare::typeshare;
|
||||
pub use wallet::Wallet;
|
||||
pub use wallet_rpc::{WalletRpc, WalletRpcProcess};
|
||||
|
||||
@ -86,6 +87,7 @@ impl From<PublicViewKey> for PublicKey {
|
||||
pub struct PublicViewKey(PublicKey);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd)]
|
||||
#[typeshare(serialized_as = "number")]
|
||||
pub struct Amount(u64);
|
||||
|
||||
// Median tx fees on Monero as found here: https://www.monero.how/monero-transaction-fees, XMR 0.000_008 * 2 (to be on the safe side)
|
||||
|
@ -7,6 +7,7 @@ use libp2p::request_response::{
|
||||
};
|
||||
use libp2p::PeerId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
const PROTOCOL: &str = "/comit/xmr/btc/bid-quote/1.0.0";
|
||||
pub type OutEvent = RequestResponseEvent<(), BidQuote>;
|
||||
@ -25,15 +26,20 @@ impl ProtocolName for BidQuoteProtocol {
|
||||
|
||||
/// Represents a quote for buying XMR.
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
#[typeshare]
|
||||
pub struct BidQuote {
|
||||
/// The price at which the maker is willing to buy at.
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
#[typeshare(serialized_as = "number")]
|
||||
pub price: bitcoin::Amount,
|
||||
/// The minimum quantity the maker is willing to buy.
|
||||
/// #[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
#[typeshare(serialized_as = "number")]
|
||||
pub min_quantity: bitcoin::Amount,
|
||||
/// The maximum quantity the maker is willing to buy.
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
#[typeshare(serialized_as = "number")]
|
||||
pub max_quantity: bitcoin::Amount,
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::api::tauri_bindings::TauriHandle;
|
||||
use crate::protocol::Database;
|
||||
use crate::{bitcoin, cli, env, monero};
|
||||
|
||||
@ -22,6 +23,7 @@ pub struct Swap {
|
||||
pub env_config: env::Config,
|
||||
pub id: Uuid,
|
||||
pub monero_receive_address: monero::Address,
|
||||
pub event_emitter: Option<TauriHandle>,
|
||||
}
|
||||
|
||||
impl Swap {
|
||||
@ -49,6 +51,7 @@ impl Swap {
|
||||
env_config,
|
||||
id,
|
||||
monero_receive_address,
|
||||
event_emitter: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,6 +76,12 @@ impl Swap {
|
||||
env_config,
|
||||
id,
|
||||
monero_receive_address,
|
||||
event_emitter: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_event_emitter(mut self, event_emitter: Option<TauriHandle>) -> Self {
|
||||
self.event_emitter = event_emitter;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::api::tauri_bindings::{TauriEmitter, TauriHandle, TauriSwapProgressEvent};
|
||||
use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund};
|
||||
use crate::cli::EventLoopHandle;
|
||||
use crate::network::cooperative_xmr_redeem_after_punish::Response::{Fullfilled, Rejected};
|
||||
@ -48,6 +49,7 @@ pub async fn run_until(
|
||||
swap.bitcoin_wallet.as_ref(),
|
||||
swap.monero_wallet.as_ref(),
|
||||
swap.monero_receive_address,
|
||||
swap.event_emitter.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -73,6 +75,7 @@ async fn next_state(
|
||||
bitcoin_wallet: &bitcoin::Wallet,
|
||||
monero_wallet: &monero::Wallet,
|
||||
monero_receive_address: monero::Address,
|
||||
event_emitter: Option<TauriHandle>,
|
||||
) -> Result<BobState> {
|
||||
tracing::debug!(%state, "Advancing state");
|
||||
|
||||
@ -120,6 +123,16 @@ async fn next_state(
|
||||
.sign_and_finalize(tx_lock.clone().into())
|
||||
.await
|
||||
.context("Failed to sign Bitcoin lock transaction")?;
|
||||
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::Started {
|
||||
btc_lock_amount: tx_lock.lock_amount(),
|
||||
// TODO: Replace this with the actual fee
|
||||
btc_tx_lock_fee: bitcoin::Amount::ZERO,
|
||||
},
|
||||
);
|
||||
|
||||
let (..) = bitcoin_wallet.broadcast(signed_tx, "lock").await?;
|
||||
|
||||
BobState::BtcLocked {
|
||||
@ -133,6 +146,15 @@ async fn next_state(
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
} => {
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::BtcLockTxInMempool {
|
||||
btc_lock_txid: state3.tx_lock_id(),
|
||||
// TODO: Replace this with the actual confirmations
|
||||
btc_lock_confirmations: 0,
|
||||
},
|
||||
);
|
||||
|
||||
let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await;
|
||||
|
||||
if let ExpiredTimelocks::None { .. } = state3.expired_timelock(bitcoin_wallet).await? {
|
||||
@ -188,6 +210,14 @@ async fn next_state(
|
||||
lock_transfer_proof,
|
||||
monero_wallet_restore_blockheight,
|
||||
} => {
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::XmrLockTxInMempool {
|
||||
xmr_lock_txid: lock_transfer_proof.tx_hash(),
|
||||
xmr_lock_tx_confirmations: 0,
|
||||
},
|
||||
);
|
||||
|
||||
let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await;
|
||||
|
||||
if let ExpiredTimelocks::None { .. } = state.expired_timelock(bitcoin_wallet).await? {
|
||||
@ -207,6 +237,7 @@ async fn next_state(
|
||||
},
|
||||
}
|
||||
}
|
||||
// TODO: Send Tauri event here everytime we receive a new confirmation
|
||||
result = tx_lock_status.wait_until_confirmed_with(state.cancel_timelock) => {
|
||||
result?;
|
||||
BobState::CancelTimelockExpired(state.cancel(monero_wallet_restore_blockheight))
|
||||
@ -217,6 +248,8 @@ async fn next_state(
|
||||
}
|
||||
}
|
||||
BobState::XmrLocked(state) => {
|
||||
event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::XmrLocked);
|
||||
|
||||
// In case we send the encrypted signature to Alice, but she doesn't give us a confirmation
|
||||
// We need to check if she still published the Bitcoin redeem transaction
|
||||
// Otherwise we risk staying stuck in "XmrLocked"
|
||||
@ -272,10 +305,21 @@ async fn next_state(
|
||||
}
|
||||
}
|
||||
BobState::BtcRedeemed(state) => {
|
||||
event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::BtcRedeemed);
|
||||
|
||||
state
|
||||
.redeem_xmr(monero_wallet, swap_id.to_string(), monero_receive_address)
|
||||
.await?;
|
||||
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::XmrRedeemInMempool {
|
||||
// TODO: Replace this with the actual txid
|
||||
xmr_redeem_txid: monero::TxHash("placeholder".to_string()),
|
||||
xmr_redeem_address: monero_receive_address,
|
||||
},
|
||||
);
|
||||
|
||||
BobState::XmrRedeemed {
|
||||
tx_lock_id: state.tx_lock_id(),
|
||||
}
|
||||
@ -288,6 +332,13 @@ async fn next_state(
|
||||
BobState::BtcCancelled(state4)
|
||||
}
|
||||
BobState::BtcCancelled(state) => {
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::BtcCancelled {
|
||||
btc_cancel_txid: state.construct_tx_cancel()?.txid(),
|
||||
},
|
||||
);
|
||||
|
||||
// Bob has cancelled the swap
|
||||
match state.expired_timelock(bitcoin_wallet).await? {
|
||||
ExpiredTimelocks::None { .. } => {
|
||||
@ -308,8 +359,24 @@ async fn next_state(
|
||||
}
|
||||
}
|
||||
}
|
||||
BobState::BtcRefunded(state4) => BobState::BtcRefunded(state4),
|
||||
BobState::BtcRefunded(state4) => {
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::BtcRefunded {
|
||||
btc_refund_txid: state4.signed_refund_transaction()?.txid(),
|
||||
},
|
||||
);
|
||||
|
||||
BobState::BtcRefunded(state4)
|
||||
}
|
||||
BobState::BtcPunished { state, tx_lock_id } => {
|
||||
event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::BtcPunished);
|
||||
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::AttemptingCooperativeRedeem,
|
||||
);
|
||||
|
||||
tracing::info!("Attempting to cooperatively redeem XMR after being punished");
|
||||
let response = event_loop_handle
|
||||
.request_cooperative_xmr_redeem(swap_id)
|
||||
@ -321,7 +388,13 @@ async fn next_state(
|
||||
"Alice has accepted our request to cooperatively redeem the XMR"
|
||||
);
|
||||
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::CooperativeRedeemAccepted,
|
||||
);
|
||||
|
||||
let s_a = monero::PrivateKey { scalar: s_a };
|
||||
|
||||
let state5 = state.attempt_cooperative_redeem(s_a);
|
||||
|
||||
match state5
|
||||
@ -329,33 +402,78 @@ async fn next_state(
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::XmrRedeemInMempool {
|
||||
xmr_redeem_txid: monero::TxHash("placeholder".to_string()),
|
||||
xmr_redeem_address: monero_receive_address,
|
||||
},
|
||||
);
|
||||
|
||||
return Ok(BobState::XmrRedeemed { tx_lock_id });
|
||||
}
|
||||
Err(error) => {
|
||||
return Err(error)
|
||||
.context("Failed to redeem XMR with revealed XMR key");
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::CooperativeRedeemRejected {
|
||||
reason: error.to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
let err: std::result::Result<_, anyhow::Error> =
|
||||
Err(error).context("Failed to redeem XMR with revealed XMR key");
|
||||
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Rejected { reason, .. }) => {
|
||||
let err = Err(reason.clone())
|
||||
.context("Alice rejected our request for cooperative XMR redeem");
|
||||
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::CooperativeRedeemRejected {
|
||||
reason: reason.to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
tracing::error!(
|
||||
?reason,
|
||||
"Alice rejected our request for cooperative XMR redeem"
|
||||
);
|
||||
return Err(reason)
|
||||
.context("Alice rejected our request for cooperative XMR redeem");
|
||||
|
||||
return err;
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
?error,
|
||||
"Failed to request cooperative XMR redeem from Alice"
|
||||
);
|
||||
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::CooperativeRedeemRejected {
|
||||
reason: error.to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
return Err(error)
|
||||
.context("Failed to request cooperative XMR redeem from Alice");
|
||||
}
|
||||
};
|
||||
}
|
||||
BobState::SafelyAborted => BobState::SafelyAborted,
|
||||
BobState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id },
|
||||
BobState::XmrRedeemed { tx_lock_id } => {
|
||||
// TODO: Replace this with the actual txid
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::XmrRedeemInMempool {
|
||||
xmr_redeem_txid: monero::TxHash("placeholder".to_string()),
|
||||
xmr_redeem_address: monero_receive_address,
|
||||
},
|
||||
);
|
||||
BobState::XmrRedeemed { tx_lock_id }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::api::request::{
|
||||
buy_xmr, cancel_and_refund, get_balance, get_current_swap, get_history, get_raw_states,
|
||||
get_swap_info, list_sellers, monero_recovery, resume_swap, suspend_current_swap, withdraw_btc,
|
||||
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, GetSwapInfoArgs, ListSellersArgs, Method,
|
||||
MoneroRecoveryArgs, ResumeArgs, WithdrawBtcArgs,
|
||||
get_current_swap, get_history, get_raw_states,
|
||||
suspend_current_swap,
|
||||
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, GetSwapInfoArgs, ListSellersArgs,
|
||||
MoneroRecoveryArgs, Request, ResumeSwapArgs, WithdrawBtcArgs,
|
||||
};
|
||||
use crate::api::Context;
|
||||
use crate::bitcoin::bitcoin_address;
|
||||
@ -42,7 +42,8 @@ pub fn register_modules(outer_context: Context) -> Result<RpcModule<Context>> {
|
||||
let swap_id = as_uuid(swap_id)
|
||||
.ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?;
|
||||
|
||||
get_swap_info(GetSwapInfoArgs { swap_id }, context)
|
||||
GetSwapInfoArgs { swap_id }
|
||||
.request(context)
|
||||
.await
|
||||
.to_jsonrpsee_result()
|
||||
})?;
|
||||
@ -60,7 +61,8 @@ pub fn register_modules(outer_context: Context) -> Result<RpcModule<Context>> {
|
||||
jsonrpsee_core::Error::Custom("force_refesh is not a boolean".to_string())
|
||||
})?;
|
||||
|
||||
get_balance(BalanceArgs { force_refresh }, context)
|
||||
BalanceArgs { force_refresh }
|
||||
.request(context)
|
||||
.await
|
||||
.to_jsonrpsee_result()
|
||||
})?;
|
||||
@ -83,7 +85,8 @@ pub fn register_modules(outer_context: Context) -> Result<RpcModule<Context>> {
|
||||
let swap_id = as_uuid(swap_id)
|
||||
.ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?;
|
||||
|
||||
resume_swap(ResumeArgs { swap_id }, context)
|
||||
ResumeSwapArgs { swap_id }
|
||||
.request(context)
|
||||
.await
|
||||
.to_jsonrpsee_result()
|
||||
})?;
|
||||
@ -98,7 +101,8 @@ pub fn register_modules(outer_context: Context) -> Result<RpcModule<Context>> {
|
||||
let swap_id = as_uuid(swap_id)
|
||||
.ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?;
|
||||
|
||||
cancel_and_refund(CancelAndRefundArgs { swap_id }, context)
|
||||
CancelAndRefundArgs { swap_id }
|
||||
.request(context)
|
||||
.await
|
||||
.to_jsonrpsee_result()
|
||||
})?;
|
||||
@ -116,7 +120,8 @@ pub fn register_modules(outer_context: Context) -> Result<RpcModule<Context>> {
|
||||
jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string())
|
||||
})?;
|
||||
|
||||
monero_recovery(MoneroRecoveryArgs { swap_id }, context)
|
||||
MoneroRecoveryArgs { swap_id }
|
||||
.request(context)
|
||||
.await
|
||||
.to_jsonrpsee_result()
|
||||
},
|
||||
@ -130,8 +135,7 @@ pub fn register_modules(outer_context: Context) -> Result<RpcModule<Context>> {
|
||||
::bitcoin::Amount::from_str_in(amount_str, ::bitcoin::Denomination::Bitcoin)
|
||||
.map_err(|_| {
|
||||
jsonrpsee_core::Error::Custom("Unable to parse amount".to_string())
|
||||
})?
|
||||
.to_sat(),
|
||||
})?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@ -145,13 +149,11 @@ pub fn register_modules(outer_context: Context) -> Result<RpcModule<Context>> {
|
||||
let withdraw_address =
|
||||
bitcoin_address::validate(withdraw_address, context.config.env_config.bitcoin_network)?;
|
||||
|
||||
withdraw_btc(
|
||||
WithdrawBtcArgs {
|
||||
amount,
|
||||
address: withdraw_address,
|
||||
},
|
||||
context,
|
||||
)
|
||||
WithdrawBtcArgs {
|
||||
amount,
|
||||
address: withdraw_address,
|
||||
}
|
||||
.request(context)
|
||||
.await
|
||||
.to_jsonrpsee_result()
|
||||
})?;
|
||||
@ -187,15 +189,12 @@ pub fn register_modules(outer_context: Context) -> Result<RpcModule<Context>> {
|
||||
})?)
|
||||
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?;
|
||||
|
||||
buy_xmr(
|
||||
BuyXmrArgs {
|
||||
seller,
|
||||
bitcoin_change_address,
|
||||
monero_receive_address,
|
||||
swap_id: Uuid::new_v4(),
|
||||
},
|
||||
context,
|
||||
)
|
||||
BuyXmrArgs {
|
||||
seller,
|
||||
bitcoin_change_address,
|
||||
monero_receive_address,
|
||||
}
|
||||
.request(context)
|
||||
.await
|
||||
.to_jsonrpsee_result()
|
||||
})?;
|
||||
@ -214,12 +213,10 @@ pub fn register_modules(outer_context: Context) -> Result<RpcModule<Context>> {
|
||||
jsonrpsee_core::Error::Custom("Could not parse valid multiaddr".to_string())
|
||||
})?;
|
||||
|
||||
list_sellers(
|
||||
ListSellersArgs {
|
||||
rendezvous_point: rendezvous_point.clone(),
|
||||
},
|
||||
context,
|
||||
)
|
||||
ListSellersArgs {
|
||||
rendezvous_point: rendezvous_point.clone(),
|
||||
}
|
||||
.request(context)
|
||||
.await
|
||||
.to_jsonrpsee_result()
|
||||
})?;
|
||||
|
@ -15,7 +15,7 @@ mod test {
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use swap::api::request::{Method, Request};
|
||||
use swap::api::request::{start_daemon, StartDaemonArgs};
|
||||
use swap::api::Context;
|
||||
|
||||
use crate::harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||
@ -39,18 +39,14 @@ mod test {
|
||||
harness_ctx: TestContext,
|
||||
) -> (Client, MakeCapturingWriter, Arc<Context>) {
|
||||
let writer = capture_logs(LevelFilter::DEBUG);
|
||||
let server_address: SocketAddr = SERVER_ADDRESS.parse().unwrap();
|
||||
|
||||
let request = Request::new(Method::StartDaemon {
|
||||
server_address: Some(server_address),
|
||||
});
|
||||
let server_address: Option<SocketAddr> = SERVER_ADDRESS.parse().unwrap().into();
|
||||
|
||||
let context = Arc::new(harness_ctx.get_bob_context().await);
|
||||
|
||||
let context_clone = context.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = request.call(context_clone).await {
|
||||
if let Err(err) = start_daemon(StartDaemonArgs { server_address }, context).await {
|
||||
println!("Failed to initialize daemon for testing: {}", err);
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user