mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-05-02 14:56:10 -04:00
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.
This commit is contained in:
parent
4cf5cf719a
commit
e46be4a9ff
12 changed files with 210 additions and 27 deletions
|
@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [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.
|
- 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.
|
- 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
|
- CLI: Encrypted signatures will be repeatedly sent until they are acknowledged by the other party
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"@tauri-apps/plugin-shell": "^2.0.0",
|
"@tauri-apps/plugin-shell": "^2.0.0",
|
||||||
"@tauri-apps/plugin-store": "^2.1.0",
|
"@tauri-apps/plugin-store": "^2.1.0",
|
||||||
"@tauri-apps/plugin-updater": "^2.0.0",
|
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||||
|
"@types/react-redux": "^7.1.34",
|
||||||
"humanize-duration": "^3.32.1",
|
"humanize-duration": "^3.32.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"multiaddr": "^10.0.1",
|
"multiaddr": "^10.0.1",
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-qr-code": "^2.0.15",
|
"react-qr-code": "^2.0.15",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.1.2",
|
||||||
"react-router-dom": "^6.24.1",
|
"react-router-dom": "^6.28.0",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"semver": "^7.6.2",
|
"semver": "^7.6.2",
|
||||||
"virtua": "^0.33.2"
|
"virtua": "^0.33.2"
|
||||||
|
|
|
@ -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.
|
||||||
|
<br />
|
||||||
|
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 <LoadingSpinnerAlert>
|
||||||
|
<AlertTitle>
|
||||||
|
Refund in progress
|
||||||
|
</AlertTitle>
|
||||||
|
The swap <TruncatedText>{backgroundRefund.swapId}</TruncatedText> is being refunded in the background.
|
||||||
|
</LoadingSpinnerAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -1,13 +1,10 @@
|
||||||
import { Button, CircularProgress } from "@material-ui/core";
|
import { Button } from "@material-ui/core";
|
||||||
import { Alert, AlertProps } from "@material-ui/lab";
|
import { Alert } from "@material-ui/lab";
|
||||||
import { TauriContextInitializationProgress } from "models/tauriModel";
|
import { TauriContextInitializationProgress } from "models/tauriModel";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
import { exhaustiveGuard } from "utils/typescriptUtils";
|
import { exhaustiveGuard } from "utils/typescriptUtils";
|
||||||
|
import { LoadingSpinnerAlert } from "./LoadingSpinnerAlert";
|
||||||
function LoadingSpinnerAlert({ ...rest }: AlertProps) {
|
|
||||||
return <Alert icon={<CircularProgress size={22} />} {...rest} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DaemonStatusAlert() {
|
export default function DaemonStatusAlert() {
|
||||||
const contextStatus = useAppSelector((s) => s.rpc.status);
|
const contextStatus = useAppSelector((s) => s.rpc.status);
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { CircularProgress } from "@material-ui/core";
|
||||||
|
import { AlertProps, Alert } from "@material-ui/lab";
|
||||||
|
|
||||||
|
export function LoadingSpinnerAlert({ ...rest }: AlertProps) {
|
||||||
|
return <Alert icon={<CircularProgress size={22} />} {...rest} />;
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import UnfinishedSwapsAlert from "../alert/UnfinishedSwapsAlert";
|
||||||
import DiscordIcon from "../icons/DiscordIcon";
|
import DiscordIcon from "../icons/DiscordIcon";
|
||||||
import LinkIconButton from "../icons/LinkIconButton";
|
import LinkIconButton from "../icons/LinkIconButton";
|
||||||
import { DISCORD_URL } from "../pages/help/ContactInfoBox";
|
import { DISCORD_URL } from "../pages/help/ContactInfoBox";
|
||||||
|
import BackgroundRefundAlert from "../alert/BackgroundRefundAlert";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
|
@ -29,6 +30,7 @@ export default function NavigationFooter() {
|
||||||
<Box className={classes.outer}>
|
<Box className={classes.outer}>
|
||||||
<FundsLeftInWalletAlert />
|
<FundsLeftInWalletAlert />
|
||||||
<UnfinishedSwapsAlert />
|
<UnfinishedSwapsAlert />
|
||||||
|
<BackgroundRefundAlert />
|
||||||
<DaemonStatusAlert />
|
<DaemonStatusAlert />
|
||||||
<MoneroWalletRpcUpdatingAlert />
|
<MoneroWalletRpcUpdatingAlert />
|
||||||
<Box className={classes.linksOuter}>
|
<Box className={classes.linksOuter}>
|
||||||
|
|
|
@ -28,10 +28,12 @@ import {
|
||||||
CheckElectrumNodeArgs,
|
CheckElectrumNodeArgs,
|
||||||
CheckElectrumNodeResponse,
|
CheckElectrumNodeResponse,
|
||||||
GetMoneroAddressesResponse,
|
GetMoneroAddressesResponse,
|
||||||
|
TauriBackgroundRefundEvent,
|
||||||
} from "models/tauriModel";
|
} from "models/tauriModel";
|
||||||
import {
|
import {
|
||||||
contextStatusEventReceived,
|
contextStatusEventReceived,
|
||||||
receivedCliLog,
|
receivedCliLog,
|
||||||
|
rpcSetBackgroundRefundState,
|
||||||
rpcSetBalance,
|
rpcSetBalance,
|
||||||
rpcSetSwapInfo,
|
rpcSetSwapInfo,
|
||||||
timelockChangeEventReceived,
|
timelockChangeEventReceived,
|
||||||
|
@ -100,6 +102,11 @@ export async function initEventListeners() {
|
||||||
console.log('Received timelock change event', event.payload);
|
console.log('Received timelock change event', event.payload);
|
||||||
store.dispatch(timelockChangeEventReceived(event.payload));
|
store.dispatch(timelockChangeEventReceived(event.payload));
|
||||||
})
|
})
|
||||||
|
|
||||||
|
listen<TauriBackgroundRefundEvent>('background-refund', (event) => {
|
||||||
|
console.log('Received background refund event', event.payload);
|
||||||
|
store.dispatch(rpcSetBackgroundRefundState(event.payload));
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function invoke<ARGS, RESPONSE>(
|
async function invoke<ARGS, RESPONSE>(
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
GetSwapInfoResponse,
|
GetSwapInfoResponse,
|
||||||
TauriContextStatusEvent,
|
TauriContextStatusEvent,
|
||||||
TauriTimelockChangeEvent,
|
TauriTimelockChangeEvent,
|
||||||
|
BackgroundRefundState,
|
||||||
} from "models/tauriModel";
|
} from "models/tauriModel";
|
||||||
import { MoneroRecoveryResponse } from "../../models/rpcModel";
|
import { MoneroRecoveryResponse } from "../../models/rpcModel";
|
||||||
import { GetSwapInfoResponseExt } from "models/tauriModelExt";
|
import { GetSwapInfoResponseExt } from "models/tauriModelExt";
|
||||||
|
@ -27,6 +28,10 @@ interface State {
|
||||||
// TODO: Reimplement this using Tauri
|
// TODO: Reimplement this using Tauri
|
||||||
updateState: false;
|
updateState: false;
|
||||||
};
|
};
|
||||||
|
backgroundRefund: {
|
||||||
|
swapId: string;
|
||||||
|
state: BackgroundRefundState;
|
||||||
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RPCSlice {
|
export interface RPCSlice {
|
||||||
|
@ -46,6 +51,7 @@ const initialState: RPCSlice = {
|
||||||
moneroWalletRpc: {
|
moneroWalletRpc: {
|
||||||
updateState: false,
|
updateState: false,
|
||||||
},
|
},
|
||||||
|
backgroundRefund: null,
|
||||||
},
|
},
|
||||||
logs: [],
|
logs: [],
|
||||||
};
|
};
|
||||||
|
@ -109,6 +115,12 @@ export const rpcSlice = createSlice({
|
||||||
rpcResetMoneroRecoveryKeys(slice) {
|
rpcResetMoneroRecoveryKeys(slice) {
|
||||||
slice.state.moneroRecovery = null;
|
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,
|
rpcSetSwapInfo,
|
||||||
rpcSetMoneroRecoveryKeys,
|
rpcSetMoneroRecoveryKeys,
|
||||||
rpcResetMoneroRecoveryKeys,
|
rpcResetMoneroRecoveryKeys,
|
||||||
|
rpcSetBackgroundRefundState,
|
||||||
timelockChangeEventReceived
|
timelockChangeEventReceived
|
||||||
} = rpcSlice.actions;
|
} = rpcSlice.actions;
|
||||||
|
|
||||||
|
|
|
@ -192,6 +192,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.14.0"
|
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":
|
"@babel/template@^7.24.7":
|
||||||
version "7.24.7"
|
version "7.24.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315"
|
||||||
|
@ -552,10 +559,10 @@
|
||||||
redux-thunk "^3.1.0"
|
redux-thunk "^3.1.0"
|
||||||
reselect "^5.1.0"
|
reselect "^5.1.0"
|
||||||
|
|
||||||
"@remix-run/router@1.17.1":
|
"@remix-run/router@1.21.0":
|
||||||
version "1.17.1"
|
version "1.21.0"
|
||||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.17.1.tgz#bf93997beb81863fde042ebd05013a2618471362"
|
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.21.0.tgz#c65ae4262bdcfe415dbd4f64ec87676e4a56e2b5"
|
||||||
integrity sha512-mCOMec4BKd6BRGBZeSnGiIgwsbLGp3yhVqAD8H+PxiRNEHgDpZb8J1TnrSDlg97t0ySKMQJTHCWBCmBpSmkF6Q==
|
integrity sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==
|
||||||
|
|
||||||
"@rollup/plugin-virtual@^3.0.2":
|
"@rollup/plugin-virtual@^3.0.2":
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
|
@ -971,6 +978,14 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
|
||||||
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
|
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":
|
"@types/humanize-duration@^3.27.4":
|
||||||
version "3.27.4"
|
version "3.27.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.27.4.tgz#51d6d278213374735440bc3749de920935e9127e"
|
resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.27.4.tgz#51d6d278213374735440bc3749de920935e9127e"
|
||||||
|
@ -1000,6 +1015,16 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@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":
|
"@types/react-transition-group@^4.2.0":
|
||||||
version "4.4.10"
|
version "4.4.10"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac"
|
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"
|
resolved "https://registry.yarnpkg.com/help-me/-/help-me-5.0.0.tgz#b1ebe63b967b74060027c2ac61f9be12d354a6f6"
|
||||||
integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==
|
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"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
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"
|
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"
|
||||||
integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==
|
integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==
|
||||||
|
|
||||||
react-router-dom@^6.24.1:
|
react-router-dom@^6.28.0:
|
||||||
version "6.24.1"
|
version "6.28.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.24.1.tgz#b1a22f7d6c5a1bfce30732bd370713f991ab4de4"
|
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.28.0.tgz#f73ebb3490e59ac9f299377062ad1d10a9f579e6"
|
||||||
integrity sha512-U19KtXqooqw967Vw0Qcn5cOvrX5Ejo9ORmOtJMzYWtCT4/WOfFLIZGGsVLxcd9UkBO0mSTZtXqhZBsWlHr7+Sg==
|
integrity sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@remix-run/router" "1.17.1"
|
"@remix-run/router" "1.21.0"
|
||||||
react-router "6.24.1"
|
react-router "6.28.0"
|
||||||
|
|
||||||
react-router@6.24.1:
|
react-router@6.28.0:
|
||||||
version "6.24.1"
|
version "6.28.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.24.1.tgz#5a3bbba0000afba68d42915456ca4c806f37a7de"
|
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.28.0.tgz#29247c86d7ba901d7e5a13aa79a96723c3e59d0d"
|
||||||
integrity sha512-PTXFXGK2pyXpHzVo3rR9H7ip4lSPZZc0bHG5CARmj65fTT6qG7sTngmb6lcYu1gf3y/8KxORoy9yn59pGpCnpg==
|
integrity sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@remix-run/router" "1.17.1"
|
"@remix-run/router" "1.21.0"
|
||||||
|
|
||||||
react-transition-group@^4.4.0:
|
react-transition-group@^4.4.0:
|
||||||
version "4.4.5"
|
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"
|
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3"
|
||||||
integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==
|
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:
|
redux@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b"
|
resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b"
|
||||||
|
|
|
@ -301,6 +301,8 @@ impl ContextBuilder {
|
||||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
||||||
.context("Failed to read seed in file")?;
|
.context("Failed to read seed in file")?;
|
||||||
|
|
||||||
|
let swap_lock = Arc::new(SwapLock::new());
|
||||||
|
|
||||||
// We initialize the Bitcoin wallet below
|
// We initialize the Bitcoin wallet below
|
||||||
// To display the progress to the user, we emit events to the Tauri frontend
|
// To display the progress to the user, we emit events to the Tauri frontend
|
||||||
self.tauri_handle
|
self.tauri_handle
|
||||||
|
@ -355,7 +357,12 @@ impl ContextBuilder {
|
||||||
// we start a background task to watch for timelock changes.
|
// we start a background task to watch for timelock changes.
|
||||||
if let Some(wallet) = bitcoin_wallet.clone() {
|
if let Some(wallet) = bitcoin_wallet.clone() {
|
||||||
if self.tauri_handle.is_some() {
|
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());
|
tokio::spawn(watcher.run());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,7 +382,7 @@ impl ContextBuilder {
|
||||||
is_testnet: self.is_testnet,
|
is_testnet: self.is_testnet,
|
||||||
data_dir,
|
data_dir,
|
||||||
},
|
},
|
||||||
swap_lock: Arc::new(SwapLock::new()),
|
swap_lock,
|
||||||
tasks: Arc::new(PendingTaskList::default()),
|
tasks: Arc::new(PendingTaskList::default()),
|
||||||
tauri_handle: self.tauri_handle,
|
tauri_handle: self.tauri_handle,
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,8 @@ const SWAP_STATE_CHANGE_EVENT_NAME: &str = "swap-database-state-update";
|
||||||
const TIMELOCK_CHANGE_EVENT_NAME: &str = "timelock-change";
|
const TIMELOCK_CHANGE_EVENT_NAME: &str = "timelock-change";
|
||||||
const CONTEXT_INIT_PROGRESS_EVENT_NAME: &str = "context-init-progress-update";
|
const CONTEXT_INIT_PROGRESS_EVENT_NAME: &str = "context-init-progress-update";
|
||||||
const BALANCE_CHANGE_EVENT_NAME: &str = "balance-change";
|
const BALANCE_CHANGE_EVENT_NAME: &str = "balance-change";
|
||||||
|
const BACKGROUND_REFUND_EVENT_NAME: &str = "background-refund";
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TauriHandle(
|
pub struct TauriHandle(
|
||||||
#[cfg(feature = "tauri")]
|
#[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 {
|
impl TauriEmitter for TauriHandle {
|
||||||
|
@ -222,6 +231,23 @@ pub struct TauriTimelockChangeEvent {
|
||||||
timelock: Option<ExpiredTimelocks>,
|
timelock: Option<ExpiredTimelocks>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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
|
/// This struct contains the settings for the Context
|
||||||
#[typeshare]
|
#[typeshare]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
|
|
@ -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::bitcoin::{ExpiredTimelocks, Wallet};
|
||||||
use crate::cli::api::tauri_bindings::TauriHandle;
|
use crate::cli::api::tauri_bindings::TauriHandle;
|
||||||
use crate::protocol::bob::BobState;
|
use crate::protocol::bob::BobState;
|
||||||
|
@ -15,6 +17,7 @@ pub struct Watcher {
|
||||||
wallet: Arc<Wallet>,
|
wallet: Arc<Wallet>,
|
||||||
database: Arc<dyn Database + Send + Sync>,
|
database: Arc<dyn Database + Send + Sync>,
|
||||||
tauri: Option<TauriHandle>,
|
tauri: Option<TauriHandle>,
|
||||||
|
swap_lock: Arc<SwapLock>,
|
||||||
/// This saves for every running swap the last known timelock status
|
/// This saves for every running swap the last known timelock status
|
||||||
cached_timelocks: HashMap<Uuid, Option<ExpiredTimelocks>>,
|
cached_timelocks: HashMap<Uuid, Option<ExpiredTimelocks>>,
|
||||||
}
|
}
|
||||||
|
@ -28,12 +31,14 @@ impl Watcher {
|
||||||
wallet: Arc<Wallet>,
|
wallet: Arc<Wallet>,
|
||||||
database: Arc<dyn Database + Send + Sync>,
|
database: Arc<dyn Database + Send + Sync>,
|
||||||
tauri: Option<TauriHandle>,
|
tauri: Option<TauriHandle>,
|
||||||
|
swap_lock: Arc<SwapLock>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
wallet,
|
wallet,
|
||||||
database,
|
database,
|
||||||
cached_timelocks: HashMap::new(),
|
cached_timelocks: HashMap::new(),
|
||||||
tauri,
|
tauri,
|
||||||
|
swap_lock,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +68,7 @@ impl Watcher {
|
||||||
.balance()
|
.balance()
|
||||||
.await
|
.await
|
||||||
.context("Failed to fetch Bitcoin balance, retrying later")?;
|
.context("Failed to fetch Bitcoin balance, retrying later")?;
|
||||||
|
|
||||||
// Emit a balance update event
|
// Emit a balance update event
|
||||||
self.tauri.emit_balance_update_event(new_balance);
|
self.tauri.emit_balance_update_event(new_balance);
|
||||||
|
|
||||||
|
@ -98,6 +104,49 @@ impl Watcher {
|
||||||
|
|
||||||
// Insert new status
|
// Insert new status
|
||||||
self.cached_timelocks.insert(swap_id, new_timelock_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(())
|
Ok(())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue