feat(cli, gui, tauri): Emit events on Monero transaction confirmation update and redeem transaction publication (#57)

We now,
- emit a Tauri event when the Monero lock transaction receives a new confirmation
- emit a Tauri event with a list of transaction hashes once we have published the Monero redeem transaction 
- gui: display the confirmations and txids

This PR closes #12.
This commit is contained in:
Einliterflasche 2024-09-18 17:53:13 +02:00 committed by GitHub
parent a1fd147850
commit 9d1151c3d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 266 additions and 153 deletions

10
node_modules/.yarn-integrity generated vendored Normal file
View file

@ -0,0 +1,10 @@
{
"systemParams": "win32-x64-127",
"modulesFolders": [],
"flags": [],
"linkedModules": [],
"topLevelPatterns": [],
"lockfileEntries": {},
"files": [],
"artifacts": {}
}

View file

@ -1,23 +1,18 @@
import { ReactNode } from "react";
import BitcoinIcon from "renderer/components/icons/BitcoinIcon"; import BitcoinIcon from "renderer/components/icons/BitcoinIcon";
import { isTestnet } from "store/config"; import { isTestnet } from "store/config";
import { getBitcoinTxExplorerUrl } from "utils/conversionUtils"; import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
import TransactionInfoBox from "./TransactionInfoBox"; import TransactionInfoBox, {
TransactionInfoBoxProps,
type Props = { } from "./TransactionInfoBox";
title: string;
txId: string;
additionalContent: ReactNode;
loading: boolean;
};
export default function BitcoinTransactionInfoBox({ txId, ...props }: Props) {
const explorerUrl = getBitcoinTxExplorerUrl(txId, isTestnet());
export default function BitcoinTransactionInfoBox({
txId,
...props
}: Omit<TransactionInfoBoxProps, "icon" | "explorerUrlCreator">) {
return ( return (
<TransactionInfoBox <TransactionInfoBox
txId={txId} txId={txId}
explorerUrl={explorerUrl} explorerUrlCreator={(txId) => getBitcoinTxExplorerUrl(txId, isTestnet())}
icon={<BitcoinIcon />} icon={<BitcoinIcon />}
{...props} {...props}
/> />

View file

@ -1,23 +1,18 @@
import { ReactNode } from "react";
import MoneroIcon from "renderer/components/icons/MoneroIcon"; import MoneroIcon from "renderer/components/icons/MoneroIcon";
import { isTestnet } from "store/config"; import { isTestnet } from "store/config";
import { getMoneroTxExplorerUrl } from "utils/conversionUtils"; import { getMoneroTxExplorerUrl } from "utils/conversionUtils";
import TransactionInfoBox from "./TransactionInfoBox"; import TransactionInfoBox, {
TransactionInfoBoxProps,
type Props = { } from "./TransactionInfoBox";
title: string;
txId: string;
additionalContent: ReactNode;
loading: boolean;
};
export default function MoneroTransactionInfoBox({ txId, ...props }: Props) {
const explorerUrl = getMoneroTxExplorerUrl(txId, isTestnet());
export default function MoneroTransactionInfoBox({
txId,
...props
}: Omit<TransactionInfoBoxProps, "icon" | "explorerUrlCreator">) {
return ( return (
<TransactionInfoBox <TransactionInfoBox
txId={txId} txId={txId}
explorerUrl={explorerUrl} explorerUrlCreator={(txid) => getMoneroTxExplorerUrl(txid, isTestnet())}
icon={<MoneroIcon />} icon={<MoneroIcon />}
{...props} {...props}
/> />

View file

@ -2,10 +2,10 @@ import { Link, Typography } from "@material-ui/core";
import { ReactNode } from "react"; import { ReactNode } from "react";
import InfoBox from "./InfoBox"; import InfoBox from "./InfoBox";
type TransactionInfoBoxProps = { export type TransactionInfoBoxProps = {
title: string; title: string;
txId: string; txId: string | null;
explorerUrl: string; explorerUrlCreator: ((txId: string) => string) | null;
additionalContent: ReactNode; additionalContent: ReactNode;
loading: boolean; loading: boolean;
icon: JSX.Element; icon: JSX.Element;
@ -14,24 +14,31 @@ type TransactionInfoBoxProps = {
export default function TransactionInfoBox({ export default function TransactionInfoBox({
title, title,
txId, txId,
explorerUrl,
additionalContent, additionalContent,
icon, icon,
loading, loading,
explorerUrlCreator,
}: TransactionInfoBoxProps) { }: TransactionInfoBoxProps) {
return ( return (
<InfoBox <InfoBox
title={title} title={title}
mainContent={<Typography variant="h5">{txId}</Typography>} mainContent={
<Typography variant="h5">
{txId ?? "Transaction ID not available"}
</Typography>
}
loading={loading} loading={loading}
additionalContent={ additionalContent={
<> <>
<Typography variant="subtitle2">{additionalContent}</Typography> <Typography variant="subtitle2">{additionalContent}</Typography>
{explorerUrlCreator != null &&
txId != null && ( // Only show the link if the txId is not null and we have a creator for the explorer URL
<Typography variant="body1"> <Typography variant="body1">
<Link href={explorerUrl} target="_blank"> <Link href={explorerUrlCreator(txId)} target="_blank">
View on explorer View on explorer
</Link> </Link>
</Typography> </Typography>
)}
</> </>
} }
icon={icon} icon={icon}

View file

@ -3,16 +3,10 @@ import { TauriSwapProgressEventContent } from "models/tauriModelExt";
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox"; import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox"; import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
export default function XmrRedeemInMempoolPage({ export default function XmrRedeemInMempoolPage(
xmr_redeem_address, state: TauriSwapProgressEventContent<"XmrRedeemInMempool">,
xmr_redeem_txid, ) {
}: TauriSwapProgressEventContent<"XmrRedeemInMempool">) { const xmr_redeem_txid = state.xmr_redeem_txids[0] ?? null;
// TODO: Reimplement this using Tauri
//const additionalContent = swap
// ? `This transaction transfers ${getSwapXmrAmount(swap).toFixed(6)} XMR to ${
// state?.bobXmrRedeemAddress
// }`
// : null;
return ( return (
<Box> <Box>
@ -30,7 +24,7 @@ export default function XmrRedeemInMempoolPage({
<MoneroTransactionInfoBox <MoneroTransactionInfoBox
title="Monero Redeem Transaction" title="Monero Redeem Transaction"
txId={xmr_redeem_txid} txId={xmr_redeem_txid}
additionalContent={`The funds have been sent to the address ${xmr_redeem_address}`} additionalContent={`The funds have been sent to the address ${state.xmr_redeem_address}`}
loading={false} loading={false}
/> />
<FeedbackInfoBox /> <FeedbackInfoBox />

View file

@ -46,3 +46,12 @@ pub enum ExpiredTimelocks {
Cancel { blocks_left: u32 }, Cancel { blocks_left: u32 },
Punish, Punish,
} }
impl ExpiredTimelocks {
/// Check whether the timelock on the cancel transaction has expired.
///
/// Retuns `true` even if the swap has already been canceled or punished.
pub fn cancel_timelock_expired(&self) -> bool {
!matches!(self, ExpiredTimelocks::None { .. })
}
}

View file

@ -135,8 +135,8 @@ pub enum TauriSwapProgressEvent {
XmrLocked, XmrLocked,
BtcRedeemed, BtcRedeemed,
XmrRedeemInMempool { XmrRedeemInMempool {
#[typeshare(serialized_as = "string")] #[typeshare(serialized_as = "Vec<string>")]
xmr_redeem_txid: monero::TxHash, xmr_redeem_txids: Vec<monero::TxHash>,
#[typeshare(serialized_as = "string")] #[typeshare(serialized_as = "string")]
xmr_redeem_address: monero::Address, xmr_redeem_address: monero::Address,
}, },

View file

@ -6,7 +6,9 @@ use ::monero::{Address, Network, PrivateKey, PublicKey};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use monero_rpc::wallet::{BlockHeight, MoneroWalletRpc as _, Refreshed}; use monero_rpc::wallet::{BlockHeight, MoneroWalletRpc as _, Refreshed};
use monero_rpc::{jsonrpc, wallet}; use monero_rpc::{jsonrpc, wallet};
use std::future::Future;
use std::ops::Div; use std::ops::Div;
use std::pin::Pin;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use tokio::sync::Mutex; use tokio::sync::Mutex;
@ -215,7 +217,18 @@ impl Wallet {
)) ))
} }
/// Wait until the specified transfer has been completed or failed.
pub async fn watch_for_transfer(&self, request: WatchRequest) -> Result<(), InsufficientFunds> { pub async fn watch_for_transfer(&self, request: WatchRequest) -> Result<(), InsufficientFunds> {
self.watch_for_transfer_with(request, None).await
}
/// Wait until the specified transfer has been completed or failed and listen to each new confirmation.
#[allow(clippy::too_many_arguments)]
pub async fn watch_for_transfer_with(
&self,
request: WatchRequest,
listener: Option<ConfirmationListener>,
) -> Result<(), InsufficientFunds> {
let WatchRequest { let WatchRequest {
conf_target, conf_target,
public_view_key, public_view_key,
@ -236,7 +249,7 @@ impl Wallet {
let check_interval = tokio::time::interval(self.sync_interval.div(10)); let check_interval = tokio::time::interval(self.sync_interval.div(10));
wait_for_confirmations( wait_for_confirmations_with(
&self.inner, &self.inner,
transfer_proof, transfer_proof,
address, address,
@ -244,6 +257,7 @@ impl Wallet {
conf_target, conf_target,
check_interval, check_interval,
self.name.clone(), self.name.clone(),
listener,
) )
.await?; .await?;
@ -332,7 +346,13 @@ pub struct WatchRequest {
pub expected: Amount, pub expected: Amount,
} }
async fn wait_for_confirmations<C: monero_rpc::wallet::MoneroWalletRpc<reqwest::Client> + Sync>( type ConfirmationListener =
Box<dyn Fn(u64) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> + Send + 'static>;
#[allow(clippy::too_many_arguments)]
async fn wait_for_confirmations_with<
C: monero_rpc::wallet::MoneroWalletRpc<reqwest::Client> + Sync,
>(
client: &Mutex<C>, client: &Mutex<C>,
transfer_proof: TransferProof, transfer_proof: TransferProof,
to_address: Address, to_address: Address,
@ -340,6 +360,7 @@ async fn wait_for_confirmations<C: monero_rpc::wallet::MoneroWalletRpc<reqwest::
conf_target: u64, conf_target: u64,
mut check_interval: Interval, mut check_interval: Interval,
wallet_name: String, wallet_name: String,
listener: Option<ConfirmationListener>,
) -> Result<(), InsufficientFunds> { ) -> Result<(), InsufficientFunds> {
let mut seen_confirmations = 0u64; let mut seen_confirmations = 0u64;
@ -405,6 +426,11 @@ async fn wait_for_confirmations<C: monero_rpc::wallet::MoneroWalletRpc<reqwest::
needed_confirmations = %conf_target, needed_confirmations = %conf_target,
"Received new confirmation for Monero lock tx" "Received new confirmation for Monero lock tx"
); );
// notify the listener we received new confirmations
if let Some(listener) = &listener {
listener(seen_confirmations).await;
}
} }
} }
@ -419,6 +445,30 @@ mod tests {
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
use tracing::metadata::LevelFilter; use tracing::metadata::LevelFilter;
async fn wait_for_confirmations<
C: monero_rpc::wallet::MoneroWalletRpc<reqwest::Client> + Sync,
>(
client: &Mutex<C>,
transfer_proof: TransferProof,
to_address: Address,
expected: Amount,
conf_target: u64,
check_interval: Interval,
wallet_name: String,
) -> Result<(), InsufficientFunds> {
wait_for_confirmations_with(
client,
transfer_proof,
to_address,
expected,
conf_target,
check_interval,
wallet_name,
None,
)
.await
}
#[tokio::test] #[tokio::test]
async fn given_exact_confirmations_does_not_fetch_tx_again() { async fn given_exact_confirmations_does_not_fetch_tx_again() {
let client = Mutex::new(DummyClient::new(vec![Ok(CheckTxKey { let client = Mutex::new(DummyClient::new(vec![Ok(CheckTxKey {
@ -435,7 +485,7 @@ mod tests {
Amount::from_piconero(100), Amount::from_piconero(100),
10, 10,
tokio::time::interval(Duration::from_millis(10)), tokio::time::interval(Duration::from_millis(10)),
"foo-wallet".to_owned() "foo-wallet".to_owned(),
) )
.await; .await;
@ -533,7 +583,7 @@ mod tests {
Amount::from_piconero(100), Amount::from_piconero(100),
5, 5,
tokio::time::interval(Duration::from_millis(10)), tokio::time::interval(Duration::from_millis(10)),
"foo-wallet".to_owned() "foo-wallet".to_owned(),
) )
.await .await
.unwrap(); .unwrap();

View file

@ -3,8 +3,8 @@ use crate::bitcoin::{
self, current_epoch, CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, self, current_epoch, CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel,
TxLock, Txid, TxLock, Txid,
}; };
use crate::monero;
use crate::monero::wallet::WatchRequest; use crate::monero::wallet::WatchRequest;
use crate::monero::{self, TxHash};
use crate::monero::{monero_private_key, TransferProof}; use crate::monero::{monero_private_key, TransferProof};
use crate::monero_ext::ScalarExt; use crate::monero_ext::ScalarExt;
use crate::protocol::{Message0, Message1, Message2, Message3, Message4, CROSS_CURVE_PROOF_SYSTEM}; use crate::protocol::{Message0, Message1, Message2, Message3, Message4, CROSS_CURVE_PROOF_SYSTEM};
@ -627,7 +627,7 @@ impl State5 {
monero_wallet: &monero::Wallet, monero_wallet: &monero::Wallet,
wallet_file_name: std::string::String, wallet_file_name: std::string::String,
monero_receive_address: monero::Address, monero_receive_address: monero::Address,
) -> Result<()> { ) -> Result<Vec<TxHash>> {
let (spend_key, view_key) = self.xmr_keys(); let (spend_key, view_key) = self.xmr_keys();
tracing::info!(%wallet_file_name, "Generating and opening Monero wallet from the extracted keys to redeem the Monero"); tracing::info!(%wallet_file_name, "Generating and opening Monero wallet from the extracted keys to redeem the Monero");
@ -652,12 +652,15 @@ impl State5 {
// Ensure that the generated wallet is synced so we have a proper balance // Ensure that the generated wallet is synced so we have a proper balance
monero_wallet.refresh(20).await?; monero_wallet.refresh(20).await?;
// Sweep (transfer all funds) to the given address
// Sweep (transfer all funds) to the Bobs Monero redeem address
let tx_hashes = monero_wallet.sweep_all(monero_receive_address).await?; let tx_hashes = monero_wallet.sweep_all(monero_receive_address).await?;
for tx_hash in tx_hashes {
for tx_hash in &tx_hashes {
tracing::info!(%monero_receive_address, txid=%tx_hash.0, "Successfully transferred XMR to wallet"); tracing::info!(%monero_receive_address, txid=%tx_hash.0, "Successfully transferred XMR to wallet");
} }
Ok(())
Ok(tx_hashes)
} }
} }

View file

@ -141,8 +141,8 @@ async fn next_state(
monero_wallet_restore_blockheight, monero_wallet_restore_blockheight,
} }
} }
// Bob has locked Btc // Bob has locked Bitcoin
// Watch for Alice to Lock Xmr or for cancel timelock to elapse // Watch for Alice to lock Monero or for cancel timelock to elapse
BobState::BtcLocked { BobState::BtcLocked {
state3, state3,
monero_wallet_restore_blockheight, monero_wallet_restore_blockheight,
@ -151,22 +151,30 @@ async fn next_state(
swap_id, swap_id,
TauriSwapProgressEvent::BtcLockTxInMempool { TauriSwapProgressEvent::BtcLockTxInMempool {
btc_lock_txid: state3.tx_lock_id(), btc_lock_txid: state3.tx_lock_id(),
// TODO: Replace this with the actual confirmations
btc_lock_confirmations: 0, btc_lock_confirmations: 0,
}, },
); );
let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await; let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await;
if let ExpiredTimelocks::None { .. } = state3.expired_timelock(bitcoin_wallet).await? { // Check whether we can cancel the swap, and do so if possible
if state3
.expired_timelock(bitcoin_wallet)
.await?
.cancel_timelock_expired()
{
let state4 = state3.cancel(monero_wallet_restore_blockheight);
return Ok(BobState::CancelTimelockExpired(state4));
};
tracing::info!("Waiting for Alice to lock Monero"); tracing::info!("Waiting for Alice to lock Monero");
let buffered_transfer_proof = db // Check if we have already buffered the XMR transfer proof
if let Some(transfer_proof) = db
.get_buffered_transfer_proof(swap_id) .get_buffered_transfer_proof(swap_id)
.await .await
.context("Failed to get buffered transfer proof")?; .context("Failed to get buffered transfer proof")?
{
if let Some(transfer_proof) = buffered_transfer_proof {
tracing::debug!(txid = %transfer_proof.tx_hash(), "Found buffered transfer proof"); tracing::debug!(txid = %transfer_proof.tx_hash(), "Found buffered transfer proof");
tracing::info!(txid = %transfer_proof.tx_hash(), "Alice locked Monero"); tracing::info!(txid = %transfer_proof.tx_hash(), "Alice locked Monero");
@ -177,11 +185,13 @@ async fn next_state(
}); });
} }
// Wait for either Alice to send the XMR transfer proof or until we can cancel the swap
let transfer_proof_watcher = event_loop_handle.recv_transfer_proof(); let transfer_proof_watcher = event_loop_handle.recv_transfer_proof();
let cancel_timelock_expires = let cancel_timelock_expires =
tx_lock_status.wait_until_confirmed_with(state3.cancel_timelock); tx_lock_status.wait_until_confirmed_with(state3.cancel_timelock);
select! { select! {
// Alice sent us the transfer proof for the Monero she locked
transfer_proof = transfer_proof_watcher => { transfer_proof = transfer_proof_watcher => {
let transfer_proof = transfer_proof?; let transfer_proof = transfer_proof?;
@ -193,6 +203,7 @@ async fn next_state(
monero_wallet_restore_blockheight monero_wallet_restore_blockheight
} }
}, },
// The cancel timelock expired before Alice locked her Monero
result = cancel_timelock_expires => { result = cancel_timelock_expires => {
result?; result?;
tracing::info!("Alice took too long to lock Monero, cancelling the swap"); tracing::info!("Alice took too long to lock Monero, cancelling the swap");
@ -201,10 +212,6 @@ async fn next_state(
BobState::CancelTimelockExpired(state4) BobState::CancelTimelockExpired(state4)
}, },
} }
} else {
let state4 = state3.cancel(monero_wallet_restore_blockheight);
BobState::CancelTimelockExpired(state4)
}
} }
BobState::XmrLockProofReceived { BobState::XmrLockProofReceived {
state, state,
@ -221,32 +228,67 @@ async fn next_state(
let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await; let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await;
if let ExpiredTimelocks::None { .. } = state.expired_timelock(bitcoin_wallet).await? { // Check if the cancel timelock has expired
// If it has, we have to cancel the swap
if state
.expired_timelock(bitcoin_wallet)
.await?
.cancel_timelock_expired()
{
return Ok(BobState::CancelTimelockExpired(
state.cancel(monero_wallet_restore_blockheight),
));
};
// Clone these so that we can move them into the listener closure
let tauri_clone = event_emitter.clone();
let transfer_proof_clone = lock_transfer_proof.clone();
let watch_request = state.lock_xmr_watch_request(lock_transfer_proof); let watch_request = state.lock_xmr_watch_request(lock_transfer_proof);
// We pass a listener to the function that get's called everytime a new confirmation is spotted.
let watch_future = monero_wallet.watch_for_transfer_with(
watch_request,
Some(Box::new(move |confirmations| {
// Clone them again so that we can move them again
let tranfer = transfer_proof_clone.clone();
let tauri = tauri_clone.clone();
// Emit an event to notify about the new confirmation
Box::pin(async move {
tauri.emit_swap_progress_event(
swap_id,
TauriSwapProgressEvent::XmrLockTxInMempool {
xmr_lock_txid: tranfer.tx_hash(),
xmr_lock_tx_confirmations: confirmations,
},
);
})
})),
);
select! { select! {
received_xmr = monero_wallet.watch_for_transfer(watch_request) => { received_xmr = watch_future => {
match received_xmr { match received_xmr {
Ok(()) => BobState::XmrLocked(state.xmr_locked(monero_wallet_restore_blockheight)), Ok(()) =>
BobState::XmrLocked(state.xmr_locked(monero_wallet_restore_blockheight)),
Err(monero::InsufficientFunds { expected, actual }) => { Err(monero::InsufficientFunds { expected, actual }) => {
// Alice locked insufficient Monero
tracing::warn!(%expected, %actual, "Insufficient Monero have been locked!"); tracing::warn!(%expected, %actual, "Insufficient Monero have been locked!");
tracing::info!(timelock = %state.cancel_timelock, "Waiting for cancel timelock to expire"); tracing::info!(timelock = %state.cancel_timelock, "Waiting for cancel timelock to expire");
// We wait for the cancel timelock to expire before we cancel the swap
// because there's no way of recovering from this state
tx_lock_status.wait_until_confirmed_with(state.cancel_timelock).await?; tx_lock_status.wait_until_confirmed_with(state.cancel_timelock).await?;
BobState::CancelTimelockExpired(state.cancel(monero_wallet_restore_blockheight)) BobState::CancelTimelockExpired(state.cancel(monero_wallet_restore_blockheight))
}, },
} }
} }
// TODO: Send Tauri event here everytime we receive a new confirmation
result = tx_lock_status.wait_until_confirmed_with(state.cancel_timelock) => { result = tx_lock_status.wait_until_confirmed_with(state.cancel_timelock) => {
result?; result?;
BobState::CancelTimelockExpired(state.cancel(monero_wallet_restore_blockheight)) BobState::CancelTimelockExpired(state.cancel(monero_wallet_restore_blockheight))
} }
} }
} else {
BobState::CancelTimelockExpired(state.cancel(monero_wallet_restore_blockheight))
}
} }
BobState::XmrLocked(state) => { BobState::XmrLocked(state) => {
event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::XmrLocked); event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::XmrLocked);
@ -260,10 +302,17 @@ async fn next_state(
let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await; let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await;
if let ExpiredTimelocks::None { .. } = state.expired_timelock(bitcoin_wallet).await? { // Check whether we can cancel the swap and do so if possible.
// Alice has locked Xmr if state
// Bob sends Alice his key .expired_timelock(bitcoin_wallet)
.await?
.cancel_timelock_expired()
{
return Ok(BobState::CancelTimelockExpired(state.cancel()));
}
// Alice has locked their Monero
// Bob sends Alice the encrypted signature which allows her to sign and broadcast the Bitcoin redeem transaction
select! { select! {
result = event_loop_handle.send_encrypted_signature(state.tx_redeem_encsig()) => { result = event_loop_handle.send_encrypted_signature(state.tx_redeem_encsig()) => {
match result { match result {
@ -277,9 +326,6 @@ async fn next_state(
BobState::CancelTimelockExpired(state.cancel()) BobState::CancelTimelockExpired(state.cancel())
} }
} }
} else {
BobState::CancelTimelockExpired(state.cancel())
}
} }
BobState::EncSigSent(state) => { BobState::EncSigSent(state) => {
// We need to make sure that Alice did not publish the redeem transaction while we were offline // We need to make sure that Alice did not publish the redeem transaction while we were offline
@ -291,7 +337,14 @@ async fn next_state(
let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await; let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await;
if let ExpiredTimelocks::None { .. } = state.expired_timelock(bitcoin_wallet).await? { if state
.expired_timelock(bitcoin_wallet)
.await?
.cancel_timelock_expired()
{
return Ok(BobState::CancelTimelockExpired(state.cancel()));
}
select! { select! {
state5 = state.watch_for_redeem_btc(bitcoin_wallet) => { state5 = state.watch_for_redeem_btc(bitcoin_wallet) => {
BobState::BtcRedeemed(state5?) BobState::BtcRedeemed(state5?)
@ -301,22 +354,18 @@ async fn next_state(
BobState::CancelTimelockExpired(state.cancel()) BobState::CancelTimelockExpired(state.cancel())
} }
} }
} else {
BobState::CancelTimelockExpired(state.cancel())
}
} }
BobState::BtcRedeemed(state) => { BobState::BtcRedeemed(state) => {
event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::BtcRedeemed); event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::BtcRedeemed);
state let xmr_redeem_txids = state
.redeem_xmr(monero_wallet, swap_id.to_string(), monero_receive_address) .redeem_xmr(monero_wallet, swap_id.to_string(), monero_receive_address)
.await?; .await?;
event_emitter.emit_swap_progress_event( event_emitter.emit_swap_progress_event(
swap_id, swap_id,
TauriSwapProgressEvent::XmrRedeemInMempool { TauriSwapProgressEvent::XmrRedeemInMempool {
// TODO: Replace this with the actual txid xmr_redeem_txids,
xmr_redeem_txid: monero::TxHash("placeholder".to_string()),
xmr_redeem_address: monero_receive_address, xmr_redeem_address: monero_receive_address,
}, },
); );
@ -402,11 +451,11 @@ async fn next_state(
.redeem_xmr(monero_wallet, swap_id.to_string(), monero_receive_address) .redeem_xmr(monero_wallet, swap_id.to_string(), monero_receive_address)
.await .await
{ {
Ok(_) => { Ok(xmr_redeem_txids) => {
event_emitter.emit_swap_progress_event( event_emitter.emit_swap_progress_event(
swap_id, swap_id,
TauriSwapProgressEvent::XmrRedeemInMempool { TauriSwapProgressEvent::XmrRedeemInMempool {
xmr_redeem_txid: monero::TxHash("placeholder".to_string()), xmr_redeem_txids,
xmr_redeem_address: monero_receive_address, xmr_redeem_address: monero_receive_address,
}, },
); );
@ -466,11 +515,12 @@ async fn next_state(
} }
BobState::SafelyAborted => BobState::SafelyAborted, BobState::SafelyAborted => BobState::SafelyAborted,
BobState::XmrRedeemed { tx_lock_id } => { BobState::XmrRedeemed { tx_lock_id } => {
// TODO: Replace this with the actual txid
event_emitter.emit_swap_progress_event( event_emitter.emit_swap_progress_event(
swap_id, swap_id,
TauriSwapProgressEvent::XmrRedeemInMempool { TauriSwapProgressEvent::XmrRedeemInMempool {
xmr_redeem_txid: monero::TxHash("placeholder".to_string()), // We don't have the txids of the redeem transaction here, so we can't emit them
// We return an empty array instead
xmr_redeem_txids: vec![],
xmr_redeem_address: monero_receive_address, xmr_redeem_address: monero_receive_address,
}, },
); );