mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
feat: Include proof of reserves in quote
This commit is contained in:
parent
b18ba95e8c
commit
a810c0108b
@ -1,9 +1,8 @@
|
|||||||
use std::fmt;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
#[jsonrpc_client::api(version = "2.0")]
|
#[jsonrpc_client::api(version = "2.0")]
|
||||||
pub trait MoneroWalletRpc {
|
pub trait MoneroWalletRpc {
|
||||||
@ -36,6 +35,18 @@ pub trait MoneroWalletRpc {
|
|||||||
async fn refresh(&self) -> Refreshed;
|
async fn refresh(&self) -> Refreshed;
|
||||||
async fn sweep_all(&self, address: String) -> SweepAll;
|
async fn sweep_all(&self, address: String) -> SweepAll;
|
||||||
async fn get_version(&self) -> Version;
|
async fn get_version(&self) -> Version;
|
||||||
|
async fn get_reserve_proof(
|
||||||
|
&self,
|
||||||
|
all: bool,
|
||||||
|
amount: Option<u64>,
|
||||||
|
message: Option<String>,
|
||||||
|
) -> GetReserveProof;
|
||||||
|
async fn check_reserve_proof(
|
||||||
|
&self,
|
||||||
|
address: String,
|
||||||
|
message: Option<String>,
|
||||||
|
signature: String,
|
||||||
|
) -> CheckReserveProof;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[jsonrpc_client::implement(MoneroWalletRpc)]
|
#[jsonrpc_client::implement(MoneroWalletRpc)]
|
||||||
@ -216,6 +227,16 @@ pub struct SweepAll {
|
|||||||
pub tx_hash_list: Vec<String>,
|
pub tx_hash_list: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct GetReserveProof {
|
||||||
|
pub signature: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct CheckReserveProof {
|
||||||
|
pub good: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Deserialize)]
|
#[derive(Debug, Copy, Clone, Deserialize)]
|
||||||
pub struct Version {
|
pub struct Version {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
|
@ -2,6 +2,7 @@ use crate::api::Context;
|
|||||||
use crate::bitcoin::{Amount, ExpiredTimelocks, TxLock};
|
use crate::bitcoin::{Amount, ExpiredTimelocks, TxLock};
|
||||||
use crate::cli::{list_sellers, EventLoop, SellerStatus};
|
use crate::cli::{list_sellers, EventLoop, SellerStatus};
|
||||||
use crate::libp2p_ext::MultiAddrExt;
|
use crate::libp2p_ext::MultiAddrExt;
|
||||||
|
use crate::monero::ReserveProof;
|
||||||
use crate::network::quote::{BidQuote, ZeroQuoteReceived};
|
use crate::network::quote::{BidQuote, ZeroQuoteReceived};
|
||||||
use crate::network::swarm;
|
use crate::network::swarm;
|
||||||
use crate::protocol::bob::{BobState, Swap};
|
use crate::protocol::bob::{BobState, Swap};
|
||||||
@ -419,6 +420,8 @@ impl Request {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let bid_quote_clone = bid_quote.clone();
|
||||||
|
|
||||||
context.tasks.clone().spawn(async move {
|
context.tasks.clone().spawn(async move {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
biased;
|
biased;
|
||||||
@ -440,6 +443,9 @@ impl Request {
|
|||||||
swap_result = async {
|
swap_result = async {
|
||||||
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size());
|
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size());
|
||||||
let estimate_fee = |amount| bitcoin_wallet.estimate_fee(TxLock::weight(), amount);
|
let estimate_fee = |amount| bitcoin_wallet.estimate_fee(TxLock::weight(), amount);
|
||||||
|
let check_reserve_proof = |reserve_proof: ReserveProof| {
|
||||||
|
monero_wallet.check_reserve_proof(reserve_proof)
|
||||||
|
};
|
||||||
|
|
||||||
let determine_amount = determine_btc_to_swap(
|
let determine_amount = determine_btc_to_swap(
|
||||||
context.config.json,
|
context.config.json,
|
||||||
@ -449,6 +455,7 @@ impl Request {
|
|||||||
max_givable,
|
max_givable,
|
||||||
|| bitcoin_wallet.sync(),
|
|| bitcoin_wallet.sync(),
|
||||||
estimate_fee,
|
estimate_fee,
|
||||||
|
check_reserve_proof,
|
||||||
);
|
);
|
||||||
|
|
||||||
let (amount, fees) = match determine_amount.await {
|
let (amount, fees) = match determine_amount.await {
|
||||||
@ -501,7 +508,7 @@ impl Request {
|
|||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"swapId": swap_id.to_string(),
|
"swapId": swap_id.to_string(),
|
||||||
"quote": bid_quote,
|
"quote": bid_quote_clone,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Method::Resume { swap_id } => {
|
Method::Resume { swap_id } => {
|
||||||
@ -763,7 +770,7 @@ impl Request {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for seller in &sellers {
|
for seller in &sellers {
|
||||||
match seller.status {
|
match seller.status.clone() {
|
||||||
SellerStatus::Online(quote) => {
|
SellerStatus::Online(quote) => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
price = %quote.price.to_string(),
|
price = %quote.price.to_string(),
|
||||||
@ -858,7 +865,7 @@ fn qr_code(value: &impl ToString) -> Result<String> {
|
|||||||
Ok(qr_code)
|
Ok(qr_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS, FFE, TFE>(
|
pub async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS, FFE, TFE, FCRP, CRP>(
|
||||||
json: bool,
|
json: bool,
|
||||||
bid_quote: BidQuote,
|
bid_quote: BidQuote,
|
||||||
get_new_address: impl Future<Output = Result<bitcoin::Address>>,
|
get_new_address: impl Future<Output = Result<bitcoin::Address>>,
|
||||||
@ -866,6 +873,7 @@ pub async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS, FFE, TFE>(
|
|||||||
max_giveable_fn: FMG,
|
max_giveable_fn: FMG,
|
||||||
sync: FS,
|
sync: FS,
|
||||||
estimate_fee: FFE,
|
estimate_fee: FFE,
|
||||||
|
check_reserve_proof: FCRP,
|
||||||
) -> Result<(Amount, Amount)>
|
) -> Result<(Amount, Amount)>
|
||||||
where
|
where
|
||||||
TB: Future<Output = Result<Amount>>,
|
TB: Future<Output = Result<Amount>>,
|
||||||
@ -874,8 +882,10 @@ where
|
|||||||
FMG: Fn() -> TMG,
|
FMG: Fn() -> TMG,
|
||||||
TS: Future<Output = Result<()>>,
|
TS: Future<Output = Result<()>>,
|
||||||
FS: Fn() -> TS,
|
FS: Fn() -> TS,
|
||||||
FFE: Fn(Amount) -> TFE,
|
|
||||||
TFE: Future<Output = Result<Amount>>,
|
TFE: Future<Output = Result<Amount>>,
|
||||||
|
FFE: Fn(Amount) -> TFE,
|
||||||
|
CRP: Future<Output = Result<bool>>,
|
||||||
|
FCRP: Fn(ReserveProof) -> CRP,
|
||||||
{
|
{
|
||||||
if bid_quote.max_quantity == Amount::ZERO {
|
if bid_quote.max_quantity == Amount::ZERO {
|
||||||
bail!(ZeroQuoteReceived)
|
bail!(ZeroQuoteReceived)
|
||||||
@ -888,6 +898,15 @@ where
|
|||||||
"Received quote",
|
"Received quote",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let Some(reserve_proof) = &bid_quote.reserve_proof {
|
||||||
|
tracing::info!("Received reserve proof");
|
||||||
|
if check_reserve_proof(reserve_proof.clone()).await? {
|
||||||
|
tracing::info!("Reserve proof is valid");
|
||||||
|
} else {
|
||||||
|
bail!("Reserve proof is invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sync().await?;
|
sync().await?;
|
||||||
let mut max_giveable = max_giveable_fn().await?;
|
let mut max_giveable = max_giveable_fn().await?;
|
||||||
|
|
||||||
|
@ -406,9 +406,12 @@ where
|
|||||||
price: ask_price,
|
price: ask_price,
|
||||||
min_quantity: bitcoin::Amount::ZERO,
|
min_quantity: bitcoin::Amount::ZERO,
|
||||||
max_quantity: bitcoin::Amount::ZERO,
|
max_quantity: bitcoin::Amount::ZERO,
|
||||||
|
reserve_proof: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let xmr_reserve_proof = self.monero_wallet.get_reserve_proof(None, None).await?;
|
||||||
|
|
||||||
if max_buy > max_bitcoin_for_monero {
|
if max_buy > max_bitcoin_for_monero {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Your Monero balance is too low to initiate a swap with the maximum swap amount {} that you have specified in your config. You can at most swap {}",
|
"Your Monero balance is too low to initiate a swap with the maximum swap amount {} that you have specified in your config. You can at most swap {}",
|
||||||
@ -418,6 +421,7 @@ where
|
|||||||
price: ask_price,
|
price: ask_price,
|
||||||
min_quantity: min_buy,
|
min_quantity: min_buy,
|
||||||
max_quantity: max_bitcoin_for_monero,
|
max_quantity: max_bitcoin_for_monero,
|
||||||
|
reserve_proof: Some(xmr_reserve_proof),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,6 +429,7 @@ where
|
|||||||
price: ask_price,
|
price: ask_price,
|
||||||
min_quantity: min_buy,
|
min_quantity: min_buy,
|
||||||
max_quantity: max_buy,
|
max_quantity: max_buy,
|
||||||
|
reserve_proof: Some(xmr_reserve_proof),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ pub struct Seller {
|
|||||||
pub multiaddr: Multiaddr,
|
pub multiaddr: Multiaddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, PartialEq, Eq, Hash, Copy, Clone, Ord, PartialOrd)]
|
#[derive(Debug, Serialize, PartialEq, Eq, Hash, Clone, Ord, PartialOrd)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
Online(BidQuote),
|
Online(BidQuote),
|
||||||
Unreachable,
|
Unreachable,
|
||||||
@ -284,7 +284,7 @@ impl EventLoop {
|
|||||||
|
|
||||||
Ok(Seller {
|
Ok(Seller {
|
||||||
multiaddr: address.clone(),
|
multiaddr: address.clone(),
|
||||||
status: Status::Online(*quote),
|
status: Status::Online(quote.clone()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
QuoteStatus::Received(Status::Unreachable) => {
|
QuoteStatus::Received(Status::Unreachable) => {
|
||||||
|
@ -199,6 +199,13 @@ pub struct TransferProof {
|
|||||||
tx_key: PrivateKey,
|
tx_key: PrivateKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||||
|
pub struct ReserveProof {
|
||||||
|
address: String,
|
||||||
|
signature: String,
|
||||||
|
message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
impl TransferProof {
|
impl TransferProof {
|
||||||
pub fn new(tx_hash: TxHash, tx_key: PrivateKey) -> Self {
|
pub fn new(tx_hash: TxHash, tx_key: PrivateKey) -> Self {
|
||||||
Self { tx_hash, tx_key }
|
Self { tx_hash, tx_key }
|
||||||
|
@ -13,6 +13,8 @@ use tokio::sync::Mutex;
|
|||||||
use tokio::time::Interval;
|
use tokio::time::Interval;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
use super::ReserveProof;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Wallet {
|
pub struct Wallet {
|
||||||
inner: Mutex<wallet::Client>,
|
inner: Mutex<wallet::Client>,
|
||||||
@ -314,6 +316,38 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
unreachable!("Loop should have returned by now");
|
unreachable!("Loop should have returned by now");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_reserve_proof(
|
||||||
|
&self,
|
||||||
|
amount: Option<u64>,
|
||||||
|
message: Option<String>,
|
||||||
|
) -> Result<ReserveProof> {
|
||||||
|
let signature = self
|
||||||
|
.inner
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.get_reserve_proof(amount.is_none(), amount, message.clone())
|
||||||
|
.await?
|
||||||
|
.signature;
|
||||||
|
|
||||||
|
let address = self.inner.lock().await.get_address(0).await?.address;
|
||||||
|
|
||||||
|
Ok(ReserveProof {
|
||||||
|
address,
|
||||||
|
signature,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_reserve_proof(&self, proof: ReserveProof) -> Result<bool> {
|
||||||
|
Ok(self
|
||||||
|
.inner
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.check_reserve_proof(proof.address, proof.message, proof.signature)
|
||||||
|
.await?
|
||||||
|
.good)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::monero::ReserveProof;
|
||||||
use crate::network::json_pull_codec::JsonPullCodec;
|
use crate::network::json_pull_codec::JsonPullCodec;
|
||||||
use crate::{asb, bitcoin, cli};
|
use crate::{asb, bitcoin, cli};
|
||||||
use libp2p::core::ProtocolName;
|
use libp2p::core::ProtocolName;
|
||||||
@ -24,7 +25,7 @@ impl ProtocolName for BidQuoteProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a quote for buying XMR.
|
/// Represents a quote for buying XMR.
|
||||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||||
pub struct BidQuote {
|
pub struct BidQuote {
|
||||||
/// The price at which the maker is willing to buy at.
|
/// The price at which the maker is willing to buy at.
|
||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
@ -35,6 +36,7 @@ pub struct BidQuote {
|
|||||||
/// The maximum quantity the maker is willing to buy.
|
/// The maximum quantity the maker is willing to buy.
|
||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
pub max_quantity: bitcoin::Amount,
|
pub max_quantity: bitcoin::Amount,
|
||||||
|
pub reserve_proof: Option<ReserveProof>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, thiserror::Error)]
|
#[derive(Clone, Copy, Debug, thiserror::Error)]
|
||||||
|
Loading…
Reference in New Issue
Block a user