From e46be4a9ff6cdcd9e4f5e46e7610c7bf038d3394 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:20:22 +0100 Subject: [PATCH] feat(gui): Refund swap in the background (#154) Swaps will now be refunded as soon as the cancel timelock expires if the GUI is running but the swap dialog is not open. --- CHANGELOG.md | 3 +- src-gui/package.json | 3 +- .../alert/BackgroundRefundAlert.tsx | 42 ++++++++++++ .../components/alert/DaemonStatusAlert.tsx | 9 +-- .../components/alert/LoadingSpinnerAlert.tsx | 6 ++ .../navigation/NavigationFooter.tsx | 2 + src-gui/src/renderer/rpc.ts | 7 ++ src-gui/src/store/features/rpcSlice.ts | 13 ++++ src-gui/yarn.lock | 64 ++++++++++++++----- swap/src/cli/api.rs | 11 +++- swap/src/cli/api/tauri_bindings.rs | 26 ++++++++ swap/src/cli/watcher.rs | 51 ++++++++++++++- 12 files changed, 210 insertions(+), 27 deletions(-) create mode 100644 src-gui/src/renderer/components/alert/BackgroundRefundAlert.tsx create mode 100644 src-gui/src/renderer/components/alert/LoadingSpinnerAlert.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 5456db9c..2936de42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Breaking change: Increased Bitcoin refund timelock from 12 hours (72 blocks) to 24 hours (144 blocks) on mainnet. This change affects the default transaction configuration and requires both CLI and ASB to be updated to maintain compatibility. Earlier versions will not be able to initiate new swaps with peers running this version. +- GUI: Swaps will now be refunded as soon as the cancel timelock expires if the GUI is running but the swap dialog is not open. +- Breaking change: Increased Bitcoin refund window from 12 hours (72 blocks) to 24 hours (144 blocks) on mainnet. This change affects the default transaction configuration and requires both CLI and ASB to be updated to maintain compatibility. Earlier versions will not be able to initiate new swaps with peers running this version. - Breaking network protocol change: The libp2p version has been upgraded to 0.53 which includes breaking network protocol changes. ASBs and CLIs will not be able to swap if one of them is on the old version. - ASB: Transfer proofs will be repeatedly sent until they are acknowledged by the other party. This fixes a bug where it'd seem to Bob as if the Alice never locked the Monero. Forcing the swap to be refunded. - CLI: Encrypted signatures will be repeatedly sent until they are acknowledged by the other party diff --git a/src-gui/package.json b/src-gui/package.json index 34a4ea5d..d4cac066 100644 --- a/src-gui/package.json +++ b/src-gui/package.json @@ -26,6 +26,7 @@ "@tauri-apps/plugin-shell": "^2.0.0", "@tauri-apps/plugin-store": "^2.1.0", "@tauri-apps/plugin-updater": "^2.0.0", + "@types/react-redux": "^7.1.34", "humanize-duration": "^3.32.1", "lodash": "^4.17.21", "multiaddr": "^10.0.1", @@ -36,7 +37,7 @@ "react-dom": "^18.2.0", "react-qr-code": "^2.0.15", "react-redux": "^9.1.2", - "react-router-dom": "^6.24.1", + "react-router-dom": "^6.28.0", "redux-persist": "^6.0.0", "semver": "^7.6.2", "virtua": "^0.33.2" diff --git a/src-gui/src/renderer/components/alert/BackgroundRefundAlert.tsx b/src-gui/src/renderer/components/alert/BackgroundRefundAlert.tsx new file mode 100644 index 00000000..eb593a97 --- /dev/null +++ b/src-gui/src/renderer/components/alert/BackgroundRefundAlert.tsx @@ -0,0 +1,42 @@ +import { BackgroundRefundState } from "models/tauriModel"; +import { useAppSelector } from "store/hooks"; +import { LoadingSpinnerAlert } from "./LoadingSpinnerAlert"; +import { AlertTitle } from "@material-ui/lab"; +import TruncatedText from "../other/TruncatedText"; +import { useSnackbar } from "notistack"; +import { useEffect } from "react"; + +export default function BackgroundRefundAlert() { + const backgroundRefund = useAppSelector(state => state.rpc.state.backgroundRefund); + const notistack = useSnackbar(); + + useEffect(() => { + // If we failed to refund, show a notification + if (backgroundRefund?.state.type === "Failed") { + notistack.enqueueSnackbar( + <> + Our attempt to refund {backgroundRefund.swapId} in the background failed. +
+ Error: {backgroundRefund.state.content.error} + , + { variant: "error", autoHideDuration: 60 * 1000 } + ); + } + + // If we successfully refunded, show a notification as well + if (backgroundRefund?.state.type === "Completed") { + notistack.enqueueSnackbar(`The swap ${backgroundRefund.swapId} has been refunded in the background.`, { variant: "success", persist: true }); + } + }, [backgroundRefund]); + + if (backgroundRefund?.state.type === "Started") { + return + + Refund in progress + + The swap {backgroundRefund.swapId} is being refunded in the background. + + } + + return null; +} \ No newline at end of file diff --git a/src-gui/src/renderer/components/alert/DaemonStatusAlert.tsx b/src-gui/src/renderer/components/alert/DaemonStatusAlert.tsx index a06e2f23..1cbc5db1 100644 --- a/src-gui/src/renderer/components/alert/DaemonStatusAlert.tsx +++ b/src-gui/src/renderer/components/alert/DaemonStatusAlert.tsx @@ -1,13 +1,10 @@ -import { Button, CircularProgress } from "@material-ui/core"; -import { Alert, AlertProps } from "@material-ui/lab"; +import { Button } from "@material-ui/core"; +import { Alert } from "@material-ui/lab"; import { TauriContextInitializationProgress } from "models/tauriModel"; import { useNavigate } from "react-router-dom"; import { useAppSelector } from "store/hooks"; import { exhaustiveGuard } from "utils/typescriptUtils"; - -function LoadingSpinnerAlert({ ...rest }: AlertProps) { - return } {...rest} />; -} +import { LoadingSpinnerAlert } from "./LoadingSpinnerAlert"; export default function DaemonStatusAlert() { const contextStatus = useAppSelector((s) => s.rpc.status); diff --git a/src-gui/src/renderer/components/alert/LoadingSpinnerAlert.tsx b/src-gui/src/renderer/components/alert/LoadingSpinnerAlert.tsx new file mode 100644 index 00000000..0adc886a --- /dev/null +++ b/src-gui/src/renderer/components/alert/LoadingSpinnerAlert.tsx @@ -0,0 +1,6 @@ +import { CircularProgress } from "@material-ui/core"; +import { AlertProps, Alert } from "@material-ui/lab"; + +export function LoadingSpinnerAlert({ ...rest }: AlertProps) { + return } {...rest} />; +} diff --git a/src-gui/src/renderer/components/navigation/NavigationFooter.tsx b/src-gui/src/renderer/components/navigation/NavigationFooter.tsx index e35efd68..a4286d8d 100644 --- a/src-gui/src/renderer/components/navigation/NavigationFooter.tsx +++ b/src-gui/src/renderer/components/navigation/NavigationFooter.tsx @@ -8,6 +8,7 @@ import UnfinishedSwapsAlert from "../alert/UnfinishedSwapsAlert"; import DiscordIcon from "../icons/DiscordIcon"; import LinkIconButton from "../icons/LinkIconButton"; import { DISCORD_URL } from "../pages/help/ContactInfoBox"; +import BackgroundRefundAlert from "../alert/BackgroundRefundAlert"; const useStyles = makeStyles((theme) => ({ outer: { @@ -29,6 +30,7 @@ export default function NavigationFooter() { + diff --git a/src-gui/src/renderer/rpc.ts b/src-gui/src/renderer/rpc.ts index 1a22003d..ca30765c 100644 --- a/src-gui/src/renderer/rpc.ts +++ b/src-gui/src/renderer/rpc.ts @@ -28,10 +28,12 @@ import { CheckElectrumNodeArgs, CheckElectrumNodeResponse, GetMoneroAddressesResponse, + TauriBackgroundRefundEvent, } from "models/tauriModel"; import { contextStatusEventReceived, receivedCliLog, + rpcSetBackgroundRefundState, rpcSetBalance, rpcSetSwapInfo, timelockChangeEventReceived, @@ -100,6 +102,11 @@ export async function initEventListeners() { console.log('Received timelock change event', event.payload); store.dispatch(timelockChangeEventReceived(event.payload)); }) + + listen('background-refund', (event) => { + console.log('Received background refund event', event.payload); + store.dispatch(rpcSetBackgroundRefundState(event.payload)); + }) } async function invoke( diff --git a/src-gui/src/store/features/rpcSlice.ts b/src-gui/src/store/features/rpcSlice.ts index a65fa61f..8eb61246 100644 --- a/src-gui/src/store/features/rpcSlice.ts +++ b/src-gui/src/store/features/rpcSlice.ts @@ -5,6 +5,7 @@ import { GetSwapInfoResponse, TauriContextStatusEvent, TauriTimelockChangeEvent, + BackgroundRefundState, } from "models/tauriModel"; import { MoneroRecoveryResponse } from "../../models/rpcModel"; import { GetSwapInfoResponseExt } from "models/tauriModelExt"; @@ -27,6 +28,10 @@ interface State { // TODO: Reimplement this using Tauri updateState: false; }; + backgroundRefund: { + swapId: string; + state: BackgroundRefundState; + } | null; } export interface RPCSlice { @@ -46,6 +51,7 @@ const initialState: RPCSlice = { moneroWalletRpc: { updateState: false, }, + backgroundRefund: null, }, logs: [], }; @@ -109,6 +115,12 @@ export const rpcSlice = createSlice({ rpcResetMoneroRecoveryKeys(slice) { slice.state.moneroRecovery = null; }, + rpcSetBackgroundRefundState(slice, action: PayloadAction<{ swap_id: string, state: BackgroundRefundState }>) { + slice.state.backgroundRefund = { + swapId: action.payload.swap_id, + state: action.payload.state, + }; + }, }, }); @@ -122,6 +134,7 @@ export const { rpcSetSwapInfo, rpcSetMoneroRecoveryKeys, rpcResetMoneroRecoveryKeys, + rpcSetBackgroundRefundState, timelockChangeEventReceived } = rpcSlice.actions; diff --git a/src-gui/yarn.lock b/src-gui/yarn.lock index 31cabec3..1eda4a1b 100644 --- a/src-gui/yarn.lock +++ b/src-gui/yarn.lock @@ -192,6 +192,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.9.2": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" @@ -552,10 +559,10 @@ redux-thunk "^3.1.0" reselect "^5.1.0" -"@remix-run/router@1.17.1": - version "1.17.1" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.17.1.tgz#bf93997beb81863fde042ebd05013a2618471362" - integrity sha512-mCOMec4BKd6BRGBZeSnGiIgwsbLGp3yhVqAD8H+PxiRNEHgDpZb8J1TnrSDlg97t0ySKMQJTHCWBCmBpSmkF6Q== +"@remix-run/router@1.21.0": + version "1.21.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.21.0.tgz#c65ae4262bdcfe415dbd4f64ec87676e4a56e2b5" + integrity sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA== "@rollup/plugin-virtual@^3.0.2": version "3.0.2" @@ -971,6 +978,14 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494" + integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/humanize-duration@^3.27.4": version "3.27.4" resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.27.4.tgz#51d6d278213374735440bc3749de920935e9127e" @@ -1000,6 +1015,16 @@ dependencies: "@types/react" "*" +"@types/react-redux@^7.1.34": + version "7.1.34" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.34.tgz#83613e1957c481521e6776beeac4fd506d11bd0e" + integrity sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + "@types/react-transition-group@^4.2.0": version "4.4.10" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" @@ -2221,7 +2246,7 @@ help-me@^5.0.0: resolved "https://registry.yarnpkg.com/help-me/-/help-me-5.0.0.tgz#b1ebe63b967b74060027c2ac61f9be12d354a6f6" integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg== -hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -3139,20 +3164,20 @@ react-refresh@^0.14.2: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== -react-router-dom@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.24.1.tgz#b1a22f7d6c5a1bfce30732bd370713f991ab4de4" - integrity sha512-U19KtXqooqw967Vw0Qcn5cOvrX5Ejo9ORmOtJMzYWtCT4/WOfFLIZGGsVLxcd9UkBO0mSTZtXqhZBsWlHr7+Sg== +react-router-dom@^6.28.0: + version "6.28.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.28.0.tgz#f73ebb3490e59ac9f299377062ad1d10a9f579e6" + integrity sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg== dependencies: - "@remix-run/router" "1.17.1" - react-router "6.24.1" + "@remix-run/router" "1.21.0" + react-router "6.28.0" -react-router@6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.24.1.tgz#5a3bbba0000afba68d42915456ca4c806f37a7de" - integrity sha512-PTXFXGK2pyXpHzVo3rR9H7ip4lSPZZc0bHG5CARmj65fTT6qG7sTngmb6lcYu1gf3y/8KxORoy9yn59pGpCnpg== +react-router@6.28.0: + version "6.28.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.28.0.tgz#29247c86d7ba901d7e5a13aa79a96723c3e59d0d" + integrity sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg== dependencies: - "@remix-run/router" "1.17.1" + "@remix-run/router" "1.21.0" react-transition-group@^4.4.0: version "4.4.5" @@ -3204,6 +3229,13 @@ redux-thunk@^3.1.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3" integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== +redux@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + redux@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" diff --git a/swap/src/cli/api.rs b/swap/src/cli/api.rs index 91d78502..6218fb98 100644 --- a/swap/src/cli/api.rs +++ b/swap/src/cli/api.rs @@ -301,6 +301,8 @@ impl ContextBuilder { let seed = Seed::from_file_or_generate(data_dir.as_path()) .context("Failed to read seed in file")?; + let swap_lock = Arc::new(SwapLock::new()); + // We initialize the Bitcoin wallet below // To display the progress to the user, we emit events to the Tauri frontend self.tauri_handle @@ -355,7 +357,12 @@ impl ContextBuilder { // we start a background task to watch for timelock changes. if let Some(wallet) = bitcoin_wallet.clone() { if self.tauri_handle.is_some() { - let watcher = Watcher::new(wallet, db.clone(), self.tauri_handle.clone()); + let watcher = Watcher::new( + wallet, + db.clone(), + self.tauri_handle.clone(), + swap_lock.clone(), + ); tokio::spawn(watcher.run()); } } @@ -375,7 +382,7 @@ impl ContextBuilder { is_testnet: self.is_testnet, data_dir, }, - swap_lock: Arc::new(SwapLock::new()), + swap_lock, tasks: Arc::new(PendingTaskList::default()), tauri_handle: self.tauri_handle, }; diff --git a/swap/src/cli/api/tauri_bindings.rs b/swap/src/cli/api/tauri_bindings.rs index 2463a722..28fd2051 100644 --- a/swap/src/cli/api/tauri_bindings.rs +++ b/swap/src/cli/api/tauri_bindings.rs @@ -15,6 +15,8 @@ const SWAP_STATE_CHANGE_EVENT_NAME: &str = "swap-database-state-update"; const TIMELOCK_CHANGE_EVENT_NAME: &str = "timelock-change"; const CONTEXT_INIT_PROGRESS_EVENT_NAME: &str = "context-init-progress-update"; const BALANCE_CHANGE_EVENT_NAME: &str = "balance-change"; +const BACKGROUND_REFUND_EVENT_NAME: &str = "background-refund"; + #[derive(Debug, Clone)] pub struct TauriHandle( #[cfg(feature = "tauri")] @@ -82,6 +84,13 @@ pub trait TauriEmitter { }, ); } + + fn emit_background_refund_event(&self, swap_id: Uuid, state: BackgroundRefundState) { + let _ = self.emit_tauri_event( + BACKGROUND_REFUND_EVENT_NAME, + TauriBackgroundRefundEvent { swap_id, state }, + ); + } } impl TauriEmitter for TauriHandle { @@ -222,6 +231,23 @@ pub struct TauriTimelockChangeEvent { timelock: Option, } +#[derive(Serialize, Clone)] +#[typeshare] +#[serde(tag = "type", content = "content")] +pub enum BackgroundRefundState { + Started, + Failed { error: String }, + Completed, +} + +#[derive(Serialize, Clone)] +#[typeshare] +pub struct TauriBackgroundRefundEvent { + #[typeshare(serialized_as = "string")] + swap_id: Uuid, + state: BackgroundRefundState, +} + /// This struct contains the settings for the Context #[typeshare] #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/swap/src/cli/watcher.rs b/swap/src/cli/watcher.rs index 0a8869d7..f75e92ef 100644 --- a/swap/src/cli/watcher.rs +++ b/swap/src/cli/watcher.rs @@ -1,4 +1,6 @@ -use super::api::tauri_bindings::TauriEmitter; +use super::api::tauri_bindings::{BackgroundRefundState, TauriEmitter}; +use super::api::SwapLock; +use super::cancel_and_refund; use crate::bitcoin::{ExpiredTimelocks, Wallet}; use crate::cli::api::tauri_bindings::TauriHandle; use crate::protocol::bob::BobState; @@ -15,6 +17,7 @@ pub struct Watcher { wallet: Arc, database: Arc, tauri: Option, + swap_lock: Arc, /// This saves for every running swap the last known timelock status cached_timelocks: HashMap>, } @@ -28,12 +31,14 @@ impl Watcher { wallet: Arc, database: Arc, tauri: Option, + swap_lock: Arc, ) -> Self { Self { wallet, database, cached_timelocks: HashMap::new(), tauri, + swap_lock, } } @@ -63,6 +68,7 @@ impl Watcher { .balance() .await .context("Failed to fetch Bitcoin balance, retrying later")?; + // Emit a balance update event self.tauri.emit_balance_update_event(new_balance); @@ -98,6 +104,49 @@ impl Watcher { // Insert new status self.cached_timelocks.insert(swap_id, new_timelock_status); + + // If the swap has to be refunded, do it in the background + if let Some(ExpiredTimelocks::Cancel { .. }) = new_timelock_status { + // If the swap is already running, we can skip the refund + // The refund will be handled by the state machine + if let Some(current_swap_id) = self.swap_lock.get_current_swap_id().await { + if current_swap_id == swap_id { + continue; + } + } + + if let Err(e) = self.swap_lock.acquire_swap_lock(swap_id).await { + tracing::error!(%e, %swap_id, "Watcher failed to refund a swap in the background because another swap is already running"); + continue; + } + + self.tauri + .emit_background_refund_event(swap_id, BackgroundRefundState::Started); + + match cancel_and_refund(swap_id, self.wallet.clone(), self.database.clone()).await { + Err(e) => { + tracing::error!(%e, %swap_id, "Watcher failed to refund a swap in the background"); + + self.tauri.emit_background_refund_event( + swap_id, + BackgroundRefundState::Failed { + error: format!("{:?}", e), + }, + ); + } + Ok(_) => { + tracing::info!(%swap_id, "Watcher has refunded a swap in the background"); + + self.tauri.emit_background_refund_event( + swap_id, + BackgroundRefundState::Completed, + ); + } + } + + // We have to release the swap lock when we are done + self.swap_lock.release_swap_lock().await?; + } } Ok(())