From 5b7e2f41d3a8f57a09600abf57bcfbb6534e6ad4 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Fri, 22 Jul 2022 16:38:11 +0200 Subject: [PATCH 01/17] Allow manual cancel & refund --- src/__tests__/store/swapSlice.spec.ts | 49 +++++----- src/main/cli/commands/buyXmrCommand.ts | 4 +- src/main/cli/commands/cancelRefundCommand.ts | 92 +++++++++++++++++++ src/main/main.ts | 5 + src/models/cliModel.ts | 2 + src/models/storeModel.ts | 4 +- .../history/alert/SwapTxLockStatusAlert.tsx | 28 ++++-- .../pages/history/table/HistoryRow.tsx | 2 +- .../pages/history/table/HistoryRowActions.tsx | 21 +++++ .../history/table/HistoryRowExpanded.tsx | 14 ++- src/store/features/swapSlice.ts | 8 +- 11 files changed, 183 insertions(+), 46 deletions(-) create mode 100644 src/main/cli/commands/cancelRefundCommand.ts diff --git a/src/__tests__/store/swapSlice.spec.ts b/src/__tests__/store/swapSlice.spec.ts index 63a01203..cacebd11 100644 --- a/src/__tests__/store/swapSlice.spec.ts +++ b/src/__tests__/store/swapSlice.spec.ts @@ -1,9 +1,5 @@ import { AnyAction } from '@reduxjs/toolkit'; -import { - SwapSlice, - SwapStateBtcLockInMempool, - SwapStateType, -} from '../../models/storeModel'; +import { SwapSlice, SwapStateBtcLockInMempool, SwapStateType } from '../../models/storeModel'; import { CliLog, CliLogAliceLockedXmr, @@ -15,13 +11,10 @@ import { CliLogRedeemedXmr, CliLogStartedSwap, CliLogWaitingForBtcDeposit, + SwapSpawnType } from '../../models/cliModel'; -import reducer, { - swapAddLog, - swapInitiate, - swapProcessExited, -} from '../../store/features/swapSlice'; +import reducer, { swapAddLog, swapInitiate, swapProcessExited } from '../../store/features/swapSlice'; import { Provider } from '../../models/apiModel'; const mWaitingForBtcDepositLog: CliLogWaitingForBtcDeposit = require('../mock_cli_logs/cli_log_waiting_for_bitcoin_deposit.json'); @@ -57,7 +50,7 @@ const initialSwapState = { logs: [], provider: null, stdOut: '', - resume: null, + spawnType: null, swapId: null, }; @@ -76,7 +69,7 @@ test('should infer correct states from happy-path logs', () => { [ swapInitiate({ provider: exampleProvider, - resume: false, + spawnType: SwapSpawnType.INIT, swapId: null, }), { @@ -87,7 +80,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: null, }, ], @@ -104,7 +97,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: null, }, ], @@ -124,7 +117,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: null, }, ], @@ -144,7 +137,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: null, }, ], @@ -164,7 +157,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }, ], @@ -187,7 +180,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }, ], @@ -211,7 +204,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }, ], @@ -236,7 +229,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }, ], @@ -262,7 +255,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }, ], @@ -286,7 +279,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }, ], @@ -311,7 +304,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }, ], @@ -341,7 +334,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }, ], @@ -379,7 +372,7 @@ test('should infer correct states from happy-path logs', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }, ], @@ -410,7 +403,7 @@ test('should infer correct states from refund-path', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }, ], @@ -435,7 +428,7 @@ test('should infer correct states from refund-path', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }, ], @@ -459,7 +452,7 @@ test('should infer correct states from refund-path', () => { }, provider: exampleProvider, stdOut: '', - resume: false, + spawnType: SwapSpawnType.INIT, swapId: '2a034c59-72bc-4b7b-839f-d32522099bcc', }; diff --git a/src/main/cli/commands/buyXmrCommand.ts b/src/main/cli/commands/buyXmrCommand.ts index f96cc5d2..a8bd6d70 100644 --- a/src/main/cli/commands/buyXmrCommand.ts +++ b/src/main/cli/commands/buyXmrCommand.ts @@ -44,7 +44,7 @@ export async function spawnBuyXmr( store.dispatch( swapInitiate({ provider, - resume: false, + spawnType: 'init', swapId: null, }) ); @@ -91,7 +91,7 @@ export async function resumeBuyXmr(swapId: string) { store.dispatch( swapInitiate({ provider, - resume: true, + spawnType: 'resume', swapId, }) ); diff --git a/src/main/cli/commands/cancelRefundCommand.ts b/src/main/cli/commands/cancelRefundCommand.ts new file mode 100644 index 00000000..1691cf73 --- /dev/null +++ b/src/main/cli/commands/cancelRefundCommand.ts @@ -0,0 +1,92 @@ +import { dialog } from 'electron'; +import { store } from '../../../store/store'; +import { + swapAddLog, + swapAppendStdOut, + swapInitiate, + swapProcessExited, +} from '../../../store/features/swapSlice'; +import { getCliLogStdOut } from '../dirs'; +import { spawnSubcommand } from '../cli'; +import logger from '../../../utils/logger'; +import { CliLog } from '../../../models/cliModel'; +import spawnBalanceCheck from './balanceCommand'; + +async function onCliLog(logs: CliLog[]) { + store.dispatch(swapAddLog(logs)); +} + +async function onStdOut(data: string) { + store.dispatch(swapAppendStdOut(data)); +} + +function onProcExit(code: number | null, signal: NodeJS.Signals | null) { + store.dispatch( + swapProcessExited({ + exitCode: code, + exitSignal: signal, + }) + ); + + spawnBalanceCheck(); +} + +export default async function spawnCancelRefund(swapId: string) { + function cancel(cb: () => void) { + return spawnSubcommand( + 'cancel', + { + 'swap-id': swapId, + }, + onCliLog, + cb, + onStdOut + ); + } + + function refund() { + return spawnSubcommand( + 'refund', + { + 'swap-id': swapId, + }, + onCliLog, + onProcExit, + onStdOut + ); + } + + try { + const provider = store + .getState() + .history.find((h) => h.swapId === swapId)?.provider; + + if (provider) { + store.dispatch( + swapInitiate({ + provider, + spawnType: 'cancel-refund', + swapId, + }) + ); + + const stdOut = await getCliLogStdOut(swapId); + let cli = await cancel(async () => { + cli = await refund(); + }); + cli.stderr.push(stdOut); + } else { + throw new Error('Could not find swap in database'); + } + } catch (err) { + logger.error({ swapId, err }, 'Failed to spawn swap cancel-refund'); + + const error = `Failed to spawn swap cancel-refund SwapID: ${swapId} Error: ${err}`; + dialog.showMessageBoxSync({ + title: 'Failed to cancel-refund command', + message: error, + type: 'error', + }); + onProcExit(null, null); + } +} diff --git a/src/main/main.ts b/src/main/main.ts index 72d0f752..2169a712 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -26,6 +26,7 @@ import watchElectrumTransactions from './blockchain/electrum'; import watchLogs from './cli/log'; import spawnListSellersCommand from './cli/commands/listSellersCommand'; import { spawnTor, stopTor } from './tor'; +import spawnCancelRefund from './cli/commands/cancelRefundCommand'; let mainWindow: BrowserWindow | null = null; @@ -165,6 +166,10 @@ ipcMain.handle( ipcMain.handle('resume-buy-xmr', (_event, swapId) => resumeBuyXmr(swapId)); +ipcMain.handle('spawn-cancel-refund', (_event, swapId) => + spawnCancelRefund(swapId) +); + ipcMain.handle('spawn-withdraw-btc', (_event, address) => spawnWithdrawBtc(address) ); diff --git a/src/models/cliModel.ts b/src/models/cliModel.ts index 70a450cf..5943d506 100644 --- a/src/models/cliModel.ts +++ b/src/models/cliModel.ts @@ -1,3 +1,5 @@ +export type SwapSpawnType = 'init' | 'resume' | 'cancel-refund'; + export interface CliLog { timestamp: string; level: 'DEBUG' | 'INFO' | 'WARN'; diff --git a/src/models/storeModel.ts b/src/models/storeModel.ts index f929bcbf..afaa5f38 100644 --- a/src/models/storeModel.ts +++ b/src/models/storeModel.ts @@ -1,4 +1,4 @@ -import { CliLog } from './cliModel'; +import { CliLog, SwapSpawnType } from './cliModel'; import { Provider } from './apiModel'; export interface SwapSlice { @@ -7,7 +7,7 @@ export interface SwapSlice { stdOut: string; processRunning: boolean; provider: Provider | null; - resume: boolean | null; + spawnType: SwapSpawnType | null; swapId: string | null; } diff --git a/src/renderer/components/pages/history/alert/SwapTxLockStatusAlert.tsx b/src/renderer/components/pages/history/alert/SwapTxLockStatusAlert.tsx index 49cc2bd2..0c3a53c1 100644 --- a/src/renderer/components/pages/history/alert/SwapTxLockStatusAlert.tsx +++ b/src/renderer/components/pages/history/alert/SwapTxLockStatusAlert.tsx @@ -17,17 +17,31 @@ function SwapAlertStatusText({ case TimelockStatusType.NONE: return ( <> - Resume the swap as soon as possible. You will be able to refund in{' '} - approx. {timelockStatus.blocksUntilRefund * 10} minutes ( - {timelockStatus.blocksUntilRefund} blocks). If you have not refunded - in approx. {timelockStatus.blocksUntilPunish * 10} minutes ( - {timelockStatus.blocksUntilPunish} blocks), you will loose your funds. + Resume the swap as soon as possible! + ); case TimelockStatusType.REFUND_EXPIRED: return ( <> - Immediately resume the swap! You only have approx.{' '} + Immediately resume the swap! You only have about{' '} {timelockStatus.blocksUntilPunish * 10} minutes ({timelockStatus.blocksUntilPunish} blocks) left to refund. After that time has passed, you will loose your funds. @@ -36,7 +50,7 @@ function SwapAlertStatusText({ default: return ( <> - Immediately resume the swap. You are in danger of losing your funds. + Immediately resume the swap! You are in immediate danger of losing your funds. ); } diff --git a/src/renderer/components/pages/history/table/HistoryRow.tsx b/src/renderer/components/pages/history/table/HistoryRow.tsx index cebb91b5..e3650f2d 100644 --- a/src/renderer/components/pages/history/table/HistoryRow.tsx +++ b/src/renderer/components/pages/history/table/HistoryRow.tsx @@ -64,7 +64,7 @@ export default function HistoryRow({ dbState }: HistoryRowProps) { {expanded ? : } - {dbState.swapId.substr(0, 5)}... + {dbState.swapId.substring(0, 5)}... diff --git a/src/renderer/components/pages/history/table/HistoryRowActions.tsx b/src/renderer/components/pages/history/table/HistoryRowActions.tsx index 16a244e5..149393fe 100644 --- a/src/renderer/components/pages/history/table/HistoryRowActions.tsx +++ b/src/renderer/components/pages/history/table/HistoryRowActions.tsx @@ -10,6 +10,8 @@ import { isMergedDoneXmrRedeemedDbState, isMergedDoneBtcRefundedDbState, isMergedDoneBtcPunishedDbState, + isSwapCancellable, + isSwapRefundable, } from '../../../../../models/databaseModel'; import IpcInvokeButton from '../../../IpcInvokeButton'; @@ -34,6 +36,25 @@ export function SwapResumeButton({ ); } +export function SwapCancelRefundButton({ + dbState, + ...props +}: { dbState: MergedDbState } & ButtonProps) { + const cancelOrRefundable = + isSwapCancellable(dbState) || isSwapRefundable(dbState); + + return ( + + Attempt manual Cancel & Refund + + ); +} + export default function HistoryRowActions({ dbState, }: { diff --git a/src/renderer/components/pages/history/table/HistoryRowExpanded.tsx b/src/renderer/components/pages/history/table/HistoryRowExpanded.tsx index 6b7d33b1..3fc46fb9 100644 --- a/src/renderer/components/pages/history/table/HistoryRowExpanded.tsx +++ b/src/renderer/components/pages/history/table/HistoryRowExpanded.tsx @@ -16,14 +16,19 @@ import { } from '../../../../../models/databaseModel'; import SwapLogFileOpenButton from './SwapLogFileOpenButton'; import DateFormatted from '../../../other/DateFormatted'; +import { SwapCancelRefundButton } from './HistoryRowActions'; const useStyles = makeStyles((theme) => ({ outer: { display: 'grid', - flexDirection: 'column', padding: theme.spacing(1), gap: theme.spacing(1), }, + actionsOuter: { + display: 'flex', + flexDirection: 'row', + gap: theme.spacing(1), + }, })); export default function HistoryRowExpanded({ @@ -84,12 +89,17 @@ export default function HistoryRowExpanded({ - + + ); diff --git a/src/store/features/swapSlice.ts b/src/store/features/swapSlice.ts index 4b51eb2e..6b2f0971 100644 --- a/src/store/features/swapSlice.ts +++ b/src/store/features/swapSlice.ts @@ -31,7 +31,7 @@ import { isCliLogStartedSwap, isCliLogWaitingForBtcDeposit, CliLog, - isCliLogAdvancingState, + isCliLogAdvancingState, SwapSpawnType } from '../../models/cliModel'; import logger from '../../utils/logger'; import { Provider } from '../../models/apiModel'; @@ -43,7 +43,7 @@ const initialState: SwapSlice = { logs: [], stdOut: '', provider: null, - resume: null, + spawnType: null, }; export const swapSlice = createSlice({ @@ -227,7 +227,7 @@ export const swapSlice = createSlice({ swap, action: PayloadAction<{ provider: Provider | null; - resume: boolean; + spawnType: SwapSpawnType; swapId: string | null; }> ) { @@ -239,7 +239,7 @@ export const swapSlice = createSlice({ swap.state = nextState; swap.logs = []; swap.provider = action.payload.provider; - swap.resume = action.payload.resume; + swap.spawnType = action.payload.spawnType; swap.swapId = action.payload.swapId; }, swapProcessExited( From 8f0436f789fc3333915c8b6c9ca46431fadc8b31 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Tue, 26 Jul 2022 16:41:44 +0200 Subject: [PATCH 02/17] Extract useTxLock hook and display txlock details on HistoryTable --- .../history/alert/SwapTxLockStatusAlert.tsx | 21 ++-------- .../history/table/HistoryRowExpanded.tsx | 29 ++++++++++++++ src/store/hooks.ts | 38 ++++++++++++++++--- 3 files changed, 65 insertions(+), 23 deletions(-) diff --git a/src/renderer/components/pages/history/alert/SwapTxLockStatusAlert.tsx b/src/renderer/components/pages/history/alert/SwapTxLockStatusAlert.tsx index 0c3a53c1..8a51444a 100644 --- a/src/renderer/components/pages/history/alert/SwapTxLockStatusAlert.tsx +++ b/src/renderer/components/pages/history/alert/SwapTxLockStatusAlert.tsx @@ -1,12 +1,11 @@ import { Alert, AlertTitle } from '@material-ui/lab/'; -import { useAppSelector } from '../../../../../store/hooks'; +import { useTimelockStatus } from '../../../../../store/hooks'; import { MergedDbState } from '../../../../../models/databaseModel'; import { SwapResumeButton } from '../table/HistoryRowActions'; import { TimelockStatus, TimelockStatusType, } from '../../../../../models/storeModel'; -import { getTimelockStatus } from '../../../../../utils/parseUtils'; function SwapAlertStatusText({ timelockStatus, @@ -50,7 +49,8 @@ function SwapAlertStatusText({ default: return ( <> - Immediately resume the swap! You are in immediate danger of losing your funds. + Immediately resume the swap! You are in danger of losing + your funds. ); } @@ -61,20 +61,7 @@ export default function SwapTxLockStatusAlert({ }: { dbState: MergedDbState; }): JSX.Element { - const timelockStatus = useAppSelector((state) => { - const txStatus = state.electrum.find( - (tx) => tx.transaction.swapId === dbState.swapId - )?.status; - - // If confirmations is null but we still have a status for the tx, we can assume that the tx is still in the mempool - const confirmations = - txStatus === undefined ? undefined : txStatus.confirmations ?? 0; - - const { cancel_timelock: refundTimelock, punish_timelock: punishTimelock } = - dbState.state.Bob.ExecutionSetupDone.state2; - - return getTimelockStatus(refundTimelock, punishTimelock, confirmations); - }); + const timelockStatus = useTimelockStatus(dbState.swapId); return ( ({ outer: { @@ -45,6 +49,14 @@ export default function HistoryRowExpanded({ const firstEnteredAt = new Date(dbState.firstEnteredDate); const { provider } = dbState; + const txLock = useAppSelector((state) => { + return state.electrum.find( + (tx) => + tx.transaction.swapId === dbState.swapId && + tx.transaction.kind === 'lock' + ); + }); + return ( @@ -86,6 +98,23 @@ export default function HistoryRowExpanded({ {provider.multiAddr} + {txLock && ( + + Bitcoin lock transaction + + + {txLock.transaction.txid} + {' '} + ({txLock.status.confirmations} confirmations) + + + )} diff --git a/src/store/hooks.ts b/src/store/hooks.ts index 006a069a..b3783663 100644 --- a/src/store/hooks.ts +++ b/src/store/hooks.ts @@ -1,6 +1,8 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; -import type { RootState, AppDispatch } from './store'; +import type { AppDispatch, RootState } from './store'; import { isSwapResumable } from '../models/databaseModel'; +import { TimelockStatus, TimelockStatusType } from '../models/storeModel'; +import { getTimelockStatus } from '../utils/parseUtils'; // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch = () => useDispatch(); @@ -17,17 +19,41 @@ export function useIsSwapRunning() { } export function useDbState(swapId?: string | null) { - const dbState = + return ( useAppSelector((s) => s.history.find((h) => h.swapId === swapId && swapId) - ) || null; - - return dbState; + ) || null + ); } export function useActiveDbState() { const swapId = useAppSelector((s) => s.swap.swapId); + return useDbState(swapId); +} + +export function useTxLock(swapId: string) { + return useAppSelector((state) => + state.electrum.find( + (tx) => tx.transaction.swapId === swapId && tx.transaction.kind === 'lock' + ) + ); +} + +export function useTimelockStatus(swapId: string): TimelockStatus { const dbState = useDbState(swapId); + const txLock = useTxLock(swapId); + + if (dbState == null || txLock == null) { + return { + type: TimelockStatusType.UNKNOWN, + }; + } + + // If confirmations is null but we still have a status for the tx, we can assume that the tx is still in the mempool + const confirmations = txLock.status.confirmations ?? 0; + + const { cancel_timelock: refundTimelock, punish_timelock: punishTimelock } = + dbState.state.Bob.ExecutionSetupDone.state2; - return dbState; + return getTimelockStatus(refundTimelock, punishTimelock, confirmations); } From 16e7cdc63cc11c13559bf16d6e6e6e8501ddd398 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Tue, 26 Jul 2022 16:42:12 +0200 Subject: [PATCH 03/17] Add some documentation to isSwapCancellable and isSwapRefundable util functions --- src/models/databaseModel.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/models/databaseModel.ts b/src/models/databaseModel.ts index 662f264a..88c1fe4e 100644 --- a/src/models/databaseModel.ts +++ b/src/models/databaseModel.ts @@ -579,6 +579,16 @@ export function isSwapResumable(dbState: MergedDbState): boolean { ); } +/* +Checks if a swap is in a state where it can possibly be cancelled + +The following conditions must be met: + - The bitcoin must be locked + - The bitcoin must not be redeemed + - The bitcoin must not be cancelled + +See: https://github.com/comit-network/xmr-btc-swap/blob/7023e75bb51ab26dff4c8fcccdc855d781ca4b15/swap/src/cli/cancel.rs#L16-L35 + */ export function isSwapCancellable(dbState: MergedDbState): boolean { return ( isBtcLockedDbState(dbState.state) && @@ -587,10 +597,23 @@ export function isSwapCancellable(dbState: MergedDbState): boolean { ); } +/* +Checks if a swap is in a state where it can possibly be refunded (meaning it's not impossible) + +The following conditions must be met: + - The bitcoin must be locked + - The bitcoin must be cancelled + - The bitcoin must not be redeemed + - The bitcoin must not be refunded + - The bitcoin must not be punished + +See: https://github.com/comit-network/xmr-btc-swap/blob/7023e75bb51ab26dff4c8fcccdc855d781ca4b15/swap/src/cli/refund.rs#L16-L34 + */ export function isSwapRefundable(dbState: MergedDbState): boolean { return ( isBtcLockedDbState(dbState.state) && isBtcCancelledDbState(dbState.state) && + !isBtcRedeemedDbState(dbState.state) && !isDoneBtcRefundedDbState(dbState.state) && !isDoneBtcPunishedDbState(dbState.state) ); From 2f2dfd6b434690b1e09f5a5e1974f7e86d9d41af Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Tue, 26 Jul 2022 17:49:26 +0200 Subject: [PATCH 04/17] Remove isCancelTimelockExpiredDbState check from guards --- src/models/cliModel.ts | 6 +++++- src/models/databaseModel.ts | 3 --- .../modal/swap/pages/init/InitiatedPage.tsx | 17 ++++++++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/models/cliModel.ts b/src/models/cliModel.ts index 5943d506..20be9fcb 100644 --- a/src/models/cliModel.ts +++ b/src/models/cliModel.ts @@ -1,4 +1,8 @@ -export type SwapSpawnType = 'init' | 'resume' | 'cancel-refund'; +export enum SwapSpawnType { + INIT = 'init', + RESUME = 'resume', + CANCEL_REFUND = 'cancel-refund', +} export interface CliLog { timestamp: string; diff --git a/src/models/databaseModel.ts b/src/models/databaseModel.ts index 88c1fe4e..e103b9ff 100644 --- a/src/models/databaseModel.ts +++ b/src/models/databaseModel.ts @@ -487,7 +487,6 @@ export function isMergedBtcCancelledDbState( return ( isExecutionSetupDoneDbState(dbState.state) && isBtcLockedDbState(dbState.state) && - isCancelTimelockExpiredDbState(dbState.state) && isBtcCancelledDbState(dbState.state) && dbState.type === DbStateType.BTC_CANCELLED ); @@ -508,7 +507,6 @@ export function isMergedDoneBtcRefundedDbState( return ( isExecutionSetupDoneDbState(dbState.state) && isBtcLockedDbState(dbState.state) && - isCancelTimelockExpiredDbState(dbState.state) && isBtcCancelledDbState(dbState.state) && isDoneBtcRefundedDbState(dbState.state) && dbState.type === DbStateType.DONE_BTC_REFUNDED @@ -530,7 +528,6 @@ export function isMergedDoneBtcPunishedDbState( return ( isExecutionSetupDoneDbState(dbState.state) && isBtcLockedDbState(dbState.state) && - isCancelTimelockExpiredDbState(dbState.state) && isBtcCancelledDbState(dbState.state) && isDoneBtcPunishedDbState(dbState.state) && dbState.type === DbStateType.DONE_BTC_PUNISHED diff --git a/src/renderer/components/modal/swap/pages/init/InitiatedPage.tsx b/src/renderer/components/modal/swap/pages/init/InitiatedPage.tsx index 00562293..cef44f3c 100644 --- a/src/renderer/components/modal/swap/pages/init/InitiatedPage.tsx +++ b/src/renderer/components/modal/swap/pages/init/InitiatedPage.tsx @@ -1,10 +1,21 @@ import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle'; import { useAppSelector } from '../../../../../../store/hooks'; +import { SwapSpawnType } from '../../../../../../models/cliModel'; export default function InitiatedPage() { - const description = useAppSelector((s) => - s.swap.resume ? 'Resuming swap' : 'Requesting quote from provider' - ); + const description = useAppSelector((s) => { + switch (s.swap.spawnType) { + case SwapSpawnType.INIT: + return 'Requesting quote from provider...'; + case SwapSpawnType.RESUME: + return 'Resuming swap...'; + case SwapSpawnType.CANCEL_REFUND: + return 'Attempting to cancel & refund swap...'; + default: + // Should never be hit + return 'Initiating swap...'; + } + }); return ; } From 430ecfc6350e7076d372e55dcbcbbe5b6e88684b Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Wed, 27 Jul 2022 13:38:04 +0200 Subject: [PATCH 05/17] Extract ConfidentialityAlert, some refactoring --- .../components/alert/ConfidentialityAlert.tsx | 10 ++++++++ .../alert/SwapTxLockAlertsBox.tsx | 4 +-- .../alert/SwapTxLockStatusAlert.tsx | 25 ++++++++----------- .../swap/{transaction => }/BitcoinQrCode.tsx | 0 .../BitcoinTransactionInfoBox.tsx | 0 .../DepositAddressInfoBox.tsx | 2 +- .../modal/swap/{transaction => }/InfoBox.tsx | 0 .../MoneroTransactionInfoBox.tsx | 0 .../{transaction => }/TransactionInfoBox.tsx | 0 .../components/modal/swap/pages/DebugPage.tsx | 7 ++---- .../swap/pages/done/BitcoinRefundedPage.tsx | 2 +- .../pages/done/XmrRedeemInMempoolPage.tsx | 2 +- .../in_progress/XmrLockInMempoolPage.tsx | 2 +- .../init/WaitingForBitcoinDepositPage.tsx | 2 +- .../pages/BitcoinWithdrawTxInMempoolPage.tsx | 2 +- .../components/pages/help/ContactInfoBox.tsx | 2 +- .../components/pages/help/DonateInfoBox.tsx | 2 +- .../components/pages/help/FeedbackInfoBox.tsx | 2 +- .../components/pages/help/TorInfoBox.tsx | 2 +- .../components/pages/history/HistoryPage.tsx | 2 +- .../pages/wallet/WithdrawWidget.tsx | 2 +- 21 files changed, 37 insertions(+), 33 deletions(-) create mode 100644 src/renderer/components/alert/ConfidentialityAlert.tsx rename src/renderer/components/{pages/history => }/alert/SwapTxLockAlertsBox.tsx (82%) rename src/renderer/components/{pages/history => }/alert/SwapTxLockStatusAlert.tsx (65%) rename src/renderer/components/modal/swap/{transaction => }/BitcoinQrCode.tsx (100%) rename src/renderer/components/modal/swap/{transaction => }/BitcoinTransactionInfoBox.tsx (100%) rename src/renderer/components/modal/swap/{transaction => }/DepositAddressInfoBox.tsx (95%) rename src/renderer/components/modal/swap/{transaction => }/InfoBox.tsx (100%) rename src/renderer/components/modal/swap/{transaction => }/MoneroTransactionInfoBox.tsx (100%) rename src/renderer/components/modal/swap/{transaction => }/TransactionInfoBox.tsx (100%) diff --git a/src/renderer/components/alert/ConfidentialityAlert.tsx b/src/renderer/components/alert/ConfidentialityAlert.tsx new file mode 100644 index 00000000..75ec1cad --- /dev/null +++ b/src/renderer/components/alert/ConfidentialityAlert.tsx @@ -0,0 +1,10 @@ +import { Alert } from '@material-ui/lab'; + +export default function ConfidentialityAlert() { + return ( + + This page contains confidential information including private keys. Keep + this information to yourself. Otherwise you will lose your money! + + ); +} diff --git a/src/renderer/components/pages/history/alert/SwapTxLockAlertsBox.tsx b/src/renderer/components/alert/SwapTxLockAlertsBox.tsx similarity index 82% rename from src/renderer/components/pages/history/alert/SwapTxLockAlertsBox.tsx rename to src/renderer/components/alert/SwapTxLockAlertsBox.tsx index e91c9e23..6c123be8 100644 --- a/src/renderer/components/pages/history/alert/SwapTxLockAlertsBox.tsx +++ b/src/renderer/components/alert/SwapTxLockAlertsBox.tsx @@ -1,6 +1,6 @@ import { Box, makeStyles } from '@material-ui/core'; -import { useAppSelector } from '../../../../../store/hooks'; -import { isSwapResumable } from '../../../../../models/databaseModel'; +import { useAppSelector } from '../../../store/hooks'; +import { isSwapResumable } from '../../../models/databaseModel'; import SwapTxLockStatusAlert from './SwapTxLockStatusAlert'; const useStyles = makeStyles((theme) => ({ diff --git a/src/renderer/components/pages/history/alert/SwapTxLockStatusAlert.tsx b/src/renderer/components/alert/SwapTxLockStatusAlert.tsx similarity index 65% rename from src/renderer/components/pages/history/alert/SwapTxLockStatusAlert.tsx rename to src/renderer/components/alert/SwapTxLockStatusAlert.tsx index 8a51444a..68ddabe0 100644 --- a/src/renderer/components/pages/history/alert/SwapTxLockStatusAlert.tsx +++ b/src/renderer/components/alert/SwapTxLockStatusAlert.tsx @@ -1,11 +1,12 @@ import { Alert, AlertTitle } from '@material-ui/lab/'; -import { useTimelockStatus } from '../../../../../store/hooks'; -import { MergedDbState } from '../../../../../models/databaseModel'; -import { SwapResumeButton } from '../table/HistoryRowActions'; +import { useTimelockStatus } from '../../../store/hooks'; +import { MergedDbState } from '../../../models/databaseModel'; +import { SwapResumeButton } from '../pages/history/table/HistoryRowActions'; import { TimelockStatus, TimelockStatusType, -} from '../../../../../models/storeModel'; +} from '../../../models/storeModel'; +import { humanizedBitcoinBlockDuration } from '../../../utils/parseUtils'; function SwapAlertStatusText({ timelockStatus, @@ -25,14 +26,12 @@ function SwapAlertStatusText({ >
  • You will be able to refund in about{' '} - {timelockStatus.blocksUntilRefund * 10} minutes ( - {timelockStatus.blocksUntilRefund} blocks). + {humanizedBitcoinBlockDuration(timelockStatus.blocksUntilRefund)}
  • If you have not refunded or completed the swap in about{' '} - {timelockStatus.blocksUntilPunish * 10} minutes ( - {timelockStatus.blocksUntilPunish} blocks), you will loose your - funds. + {humanizedBitcoinBlockDuration(timelockStatus.blocksUntilPunish)}, + you will lose your funds.
  • @@ -41,16 +40,14 @@ function SwapAlertStatusText({ return ( <> Immediately resume the swap! You only have about{' '} - {timelockStatus.blocksUntilPunish * 10} - minutes ({timelockStatus.blocksUntilPunish} blocks) left to refund. - After that time has passed, you will loose your funds. + {humanizedBitcoinBlockDuration(timelockStatus.blocksUntilPunish)} left + to refund. After that time has passed, you will lose your funds. ); default: return ( <> - Immediately resume the swap! You are in danger of losing - your funds. + Immediately resume the swap! You are in danger of losing your funds. ); } diff --git a/src/renderer/components/modal/swap/transaction/BitcoinQrCode.tsx b/src/renderer/components/modal/swap/BitcoinQrCode.tsx similarity index 100% rename from src/renderer/components/modal/swap/transaction/BitcoinQrCode.tsx rename to src/renderer/components/modal/swap/BitcoinQrCode.tsx diff --git a/src/renderer/components/modal/swap/transaction/BitcoinTransactionInfoBox.tsx b/src/renderer/components/modal/swap/BitcoinTransactionInfoBox.tsx similarity index 100% rename from src/renderer/components/modal/swap/transaction/BitcoinTransactionInfoBox.tsx rename to src/renderer/components/modal/swap/BitcoinTransactionInfoBox.tsx diff --git a/src/renderer/components/modal/swap/transaction/DepositAddressInfoBox.tsx b/src/renderer/components/modal/swap/DepositAddressInfoBox.tsx similarity index 95% rename from src/renderer/components/modal/swap/transaction/DepositAddressInfoBox.tsx rename to src/renderer/components/modal/swap/DepositAddressInfoBox.tsx index 61b8bedd..04c69333 100644 --- a/src/renderer/components/modal/swap/transaction/DepositAddressInfoBox.tsx +++ b/src/renderer/components/modal/swap/DepositAddressInfoBox.tsx @@ -2,7 +2,7 @@ import { ReactNode } from 'react'; import { Box, makeStyles, Typography } from '@material-ui/core'; import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined'; import InfoBox from './InfoBox'; -import ClipboardIconButton from '../ClipbiardIconButton'; +import ClipboardIconButton from './ClipbiardIconButton'; import BitcoinQrCode from './BitcoinQrCode'; type Props = { diff --git a/src/renderer/components/modal/swap/transaction/InfoBox.tsx b/src/renderer/components/modal/swap/InfoBox.tsx similarity index 100% rename from src/renderer/components/modal/swap/transaction/InfoBox.tsx rename to src/renderer/components/modal/swap/InfoBox.tsx diff --git a/src/renderer/components/modal/swap/transaction/MoneroTransactionInfoBox.tsx b/src/renderer/components/modal/swap/MoneroTransactionInfoBox.tsx similarity index 100% rename from src/renderer/components/modal/swap/transaction/MoneroTransactionInfoBox.tsx rename to src/renderer/components/modal/swap/MoneroTransactionInfoBox.tsx diff --git a/src/renderer/components/modal/swap/transaction/TransactionInfoBox.tsx b/src/renderer/components/modal/swap/TransactionInfoBox.tsx similarity index 100% rename from src/renderer/components/modal/swap/transaction/TransactionInfoBox.tsx rename to src/renderer/components/modal/swap/TransactionInfoBox.tsx diff --git a/src/renderer/components/modal/swap/pages/DebugPage.tsx b/src/renderer/components/modal/swap/pages/DebugPage.tsx index 85682906..c6d0425c 100644 --- a/src/renderer/components/modal/swap/pages/DebugPage.tsx +++ b/src/renderer/components/modal/swap/pages/DebugPage.tsx @@ -1,7 +1,7 @@ import { Box, DialogContentText, Typography } from '@material-ui/core'; -import { Alert } from '@material-ui/lab'; import PaperTextBox from '../../PaperTextBox'; import { useActiveDbState, useAppSelector } from '../../../../../store/hooks'; +import ConfidentialityAlert from '../../../alert/ConfidentialityAlert'; export default function DebugPage() { const swapStdOut = useAppSelector((s) => s.swap.stdOut); @@ -21,10 +21,7 @@ export default function DebugPage() { return ( - - This page contains confidential information including private keys. - Keep this information to yourself. Otherwise you will lose your money! - +
    Swap standard output diff --git a/src/renderer/components/modal/swap/pages/done/BitcoinRefundedPage.tsx b/src/renderer/components/modal/swap/pages/done/BitcoinRefundedPage.tsx index 27bdbc39..ada37987 100644 --- a/src/renderer/components/modal/swap/pages/done/BitcoinRefundedPage.tsx +++ b/src/renderer/components/modal/swap/pages/done/BitcoinRefundedPage.tsx @@ -1,6 +1,6 @@ import { Box, DialogContentText } from '@material-ui/core'; import { SwapStateBtcRefunded } from 'models/storeModel'; -import BitcoinTransactionInfoBox from '../../transaction/BitcoinTransactionInfoBox'; +import BitcoinTransactionInfoBox from '../../BitcoinTransactionInfoBox'; import { useActiveDbState } from '../../../../../../store/hooks'; export default function BitcoinRefundedPage({ diff --git a/src/renderer/components/modal/swap/pages/done/XmrRedeemInMempoolPage.tsx b/src/renderer/components/modal/swap/pages/done/XmrRedeemInMempoolPage.tsx index f29c437e..be30da23 100644 --- a/src/renderer/components/modal/swap/pages/done/XmrRedeemInMempoolPage.tsx +++ b/src/renderer/components/modal/swap/pages/done/XmrRedeemInMempoolPage.tsx @@ -2,7 +2,7 @@ import { Box, DialogContentText } from '@material-ui/core'; import { SwapStateXmrRedeemInMempool } from '../../../../../../models/storeModel'; import { pionerosToXmr } from '../../../../../../utils/conversionUtils'; import { useActiveDbState } from '../../../../../../store/hooks'; -import MoneroTransactionInfoBox from '../../transaction/MoneroTransactionInfoBox'; +import MoneroTransactionInfoBox from '../../MoneroTransactionInfoBox'; type XmrRedeemInMempoolPageProps = { state: SwapStateXmrRedeemInMempool | null; diff --git a/src/renderer/components/modal/swap/pages/in_progress/XmrLockInMempoolPage.tsx b/src/renderer/components/modal/swap/pages/in_progress/XmrLockInMempoolPage.tsx index a9868c35..973c4ac0 100644 --- a/src/renderer/components/modal/swap/pages/in_progress/XmrLockInMempoolPage.tsx +++ b/src/renderer/components/modal/swap/pages/in_progress/XmrLockInMempoolPage.tsx @@ -1,6 +1,6 @@ import { Box, DialogContentText } from '@material-ui/core'; import { SwapStateXmrLockInMempool } from '../../../../../../models/storeModel'; -import MoneroTransactionInfoBox from '../../transaction/MoneroTransactionInfoBox'; +import MoneroTransactionInfoBox from '../../MoneroTransactionInfoBox'; type XmrLockTxInMempoolPageProps = { state: SwapStateXmrLockInMempool; diff --git a/src/renderer/components/modal/swap/pages/init/WaitingForBitcoinDepositPage.tsx b/src/renderer/components/modal/swap/pages/init/WaitingForBitcoinDepositPage.tsx index c6278101..c01eebd2 100644 --- a/src/renderer/components/modal/swap/pages/init/WaitingForBitcoinDepositPage.tsx +++ b/src/renderer/components/modal/swap/pages/init/WaitingForBitcoinDepositPage.tsx @@ -5,7 +5,7 @@ import { Typography, } from '@material-ui/core'; import { SwapStateWaitingForBtcDeposit } from '../../../../../../models/storeModel'; -import DepositAddressInfoBox from '../../transaction/DepositAddressInfoBox'; +import DepositAddressInfoBox from '../../DepositAddressInfoBox'; import BitcoinIcon from '../../../../icons/BitcoinIcon'; import { btcToSats, satsToBtc } from '../../../../../../utils/conversionUtils'; diff --git a/src/renderer/components/modal/wallet/pages/BitcoinWithdrawTxInMempoolPage.tsx b/src/renderer/components/modal/wallet/pages/BitcoinWithdrawTxInMempoolPage.tsx index cedd9703..a27e10af 100644 --- a/src/renderer/components/modal/wallet/pages/BitcoinWithdrawTxInMempoolPage.tsx +++ b/src/renderer/components/modal/wallet/pages/BitcoinWithdrawTxInMempoolPage.tsx @@ -1,6 +1,6 @@ import { Button, DialogActions, DialogContentText } from '@material-ui/core'; import { WithdrawStateWithdrawTxInMempool } from '../../../../../models/storeModel'; -import BitcoinTransactionInfoBox from '../../swap/transaction/BitcoinTransactionInfoBox'; +import BitcoinTransactionInfoBox from '../../swap/BitcoinTransactionInfoBox'; import { useAppSelector } from '../../../../../store/hooks'; import WithdrawDialogContent from '../WithdrawDialogContent'; diff --git a/src/renderer/components/pages/help/ContactInfoBox.tsx b/src/renderer/components/pages/help/ContactInfoBox.tsx index 96ba6265..e079f372 100644 --- a/src/renderer/components/pages/help/ContactInfoBox.tsx +++ b/src/renderer/components/pages/help/ContactInfoBox.tsx @@ -1,5 +1,5 @@ import { Box, Button, makeStyles, Typography } from '@material-ui/core'; -import InfoBox from '../../modal/swap/transaction/InfoBox'; +import InfoBox from '../../modal/swap/InfoBox'; const useStyles = makeStyles((theme) => ({ spacedBox: { diff --git a/src/renderer/components/pages/help/DonateInfoBox.tsx b/src/renderer/components/pages/help/DonateInfoBox.tsx index 58fbb71c..a3dd510a 100644 --- a/src/renderer/components/pages/help/DonateInfoBox.tsx +++ b/src/renderer/components/pages/help/DonateInfoBox.tsx @@ -1,5 +1,5 @@ import { Typography } from '@material-ui/core'; -import DepositAddressInfoBox from '../../modal/swap/transaction/DepositAddressInfoBox'; +import DepositAddressInfoBox from '../../modal/swap/DepositAddressInfoBox'; import MoneroIcon from '../../icons/MoneroIcon'; const XMR_DONATE_ADDRESS = diff --git a/src/renderer/components/pages/help/FeedbackInfoBox.tsx b/src/renderer/components/pages/help/FeedbackInfoBox.tsx index 9a9ee196..3a1565d6 100644 --- a/src/renderer/components/pages/help/FeedbackInfoBox.tsx +++ b/src/renderer/components/pages/help/FeedbackInfoBox.tsx @@ -1,5 +1,5 @@ import { Button, Typography } from '@material-ui/core'; -import InfoBox from '../../modal/swap/transaction/InfoBox'; +import InfoBox from '../../modal/swap/InfoBox'; const FEEDBACK_URL = 'https://unstoppableswap.aidaform.com/feedback'; diff --git a/src/renderer/components/pages/help/TorInfoBox.tsx b/src/renderer/components/pages/help/TorInfoBox.tsx index 69cddd37..171db860 100644 --- a/src/renderer/components/pages/help/TorInfoBox.tsx +++ b/src/renderer/components/pages/help/TorInfoBox.tsx @@ -3,7 +3,7 @@ import IpcInvokeButton from 'renderer/components/IpcInvokeButton'; import { useAppSelector } from 'store/hooks'; import StopIcon from '@material-ui/icons/Stop'; import PlayArrowIcon from '@material-ui/icons/PlayArrow'; -import InfoBox from '../../modal/swap/transaction/InfoBox'; +import InfoBox from '../../modal/swap/InfoBox'; const useStyles = makeStyles((theme) => ({ actionsOuter: { diff --git a/src/renderer/components/pages/history/HistoryPage.tsx b/src/renderer/components/pages/history/HistoryPage.tsx index fe458994..1ebd224b 100644 --- a/src/renderer/components/pages/history/HistoryPage.tsx +++ b/src/renderer/components/pages/history/HistoryPage.tsx @@ -2,7 +2,7 @@ import { Typography } from '@material-ui/core'; import HistoryTable from './table/HistoryTable'; import SwapDialog from '../../modal/swap/SwapDialog'; import { useIsSwapRunning } from '../../../../store/hooks'; -import SwapTxLockAlertsBox from './alert/SwapTxLockAlertsBox'; +import SwapTxLockAlertsBox from '../../alert/SwapTxLockAlertsBox'; export default function HistoryPage() { const showDialog = useIsSwapRunning(); diff --git a/src/renderer/components/pages/wallet/WithdrawWidget.tsx b/src/renderer/components/pages/wallet/WithdrawWidget.tsx index bc3fd50b..08982a63 100644 --- a/src/renderer/components/pages/wallet/WithdrawWidget.tsx +++ b/src/renderer/components/pages/wallet/WithdrawWidget.tsx @@ -6,7 +6,7 @@ import BitcoinIcon from '../../icons/BitcoinIcon'; import WithdrawDialog from '../../modal/wallet/WithdrawDialog'; import WalletRefreshButton from './WalletRefreshButton'; import { isWithdrawState } from '../../../../models/storeModel'; -import InfoBox from '../../modal/swap/transaction/InfoBox'; +import InfoBox from '../../modal/swap/InfoBox'; const useStyles = makeStyles((theme) => ({ title: { From 4c0abde2af069b371fbae0589d8a7a277ffee677 Mon Sep 17 00:00:00 2001 From: binarybaron Date: Thu, 28 Jul 2022 16:03:31 +0200 Subject: [PATCH 06/17] Humanize bitcoin block durations, SwapMightBeCancelledAlert --- package-lock.json | 24 ++++++ package.json | 2 + src/__tests__/store/swapSlice.spec.ts | 14 +++- src/main/cli/commands/buyXmrCommand.ts | 6 +- src/main/cli/commands/cancelRefundCommand.ts | 14 +++- .../components/alert/ConfidentialityAlert.tsx | 2 +- .../alert/SwapMightBeCancelledAlert.tsx | 82 +++++++++++++++++++ .../alert/SwapTxLockStatusAlert.tsx | 25 +++--- .../BitcoinLockTxInMempoolPage.tsx | 12 ++- .../other/HumanizedBitcoinBlockDuration.tsx | 17 ++++ src/store/features/swapSlice.ts | 3 +- 11 files changed, 179 insertions(+), 22 deletions(-) create mode 100644 src/renderer/components/alert/SwapMightBeCancelledAlert.tsx create mode 100644 src/renderer/components/other/HumanizedBitcoinBlockDuration.tsx diff --git a/package-lock.json b/package-lock.json index 4f23d04b..5d09d71c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "blocked-at": "^1.2.0", "electron-redux": "2.0.0-alpha.8", "electrum-cash": "^2.0.10", + "humanize-duration": "^3.27.2", "multiaddr": "^10.0.1", "p-queue": "^6.6.2", "path-browserify": "^1.0.1", @@ -39,6 +40,7 @@ "@types/better-sqlite3": "^7.4.2", "@types/blocked-at": "^1.0.1", "@types/download": "^8.0.1", + "@types/humanize-duration": "^3.27.1", "@types/jest": "^27.0.3", "@types/lodash": "^4.14.178", "@types/mini-css-extract-plugin": "^2.4.0", @@ -2389,6 +2391,12 @@ "@types/node": "*" } }, + "node_modules/@types/humanize-duration": { + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/@types/humanize-duration/-/humanize-duration-3.27.1.tgz", + "integrity": "sha512-K3e+NZlpCKd6Bd/EIdqjFJRFHbrq5TzPPLwREk5Iv/YoIjQrs6ljdAUCo+Lb2xFlGNOjGSE0dqsVD19cZL137w==", + "dev": true + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -10280,6 +10288,11 @@ "node": ">=10.17.0" } }, + "node_modules/humanize-duration": { + "version": "3.27.2", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.2.tgz", + "integrity": "sha512-A15OmA3FLFRnehvF4ZMocsxTZYvHq4ze7L+AgR1DeHw0xC9vMd4euInY83uqGU9/XXKNnVIEeKc1R8G8nKqtzg==" + }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -20389,6 +20402,12 @@ "@types/node": "*" } }, + "@types/humanize-duration": { + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/@types/humanize-duration/-/humanize-duration-3.27.1.tgz", + "integrity": "sha512-K3e+NZlpCKd6Bd/EIdqjFJRFHbrq5TzPPLwREk5Iv/YoIjQrs6ljdAUCo+Lb2xFlGNOjGSE0dqsVD19cZL137w==", + "dev": true + }, "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -26388,6 +26407,11 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, + "humanize-duration": { + "version": "3.27.2", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.2.tgz", + "integrity": "sha512-A15OmA3FLFRnehvF4ZMocsxTZYvHq4ze7L+AgR1DeHw0xC9vMd4euInY83uqGU9/XXKNnVIEeKc1R8G8nKqtzg==" + }, "humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", diff --git a/package.json b/package.json index b53f7bf0..4c141b2b 100644 --- a/package.json +++ b/package.json @@ -158,6 +158,7 @@ "@types/better-sqlite3": "^7.4.2", "@types/blocked-at": "^1.0.1", "@types/download": "^8.0.1", + "@types/humanize-duration": "^3.27.1", "@types/jest": "^27.0.3", "@types/lodash": "^4.14.178", "@types/mini-css-extract-plugin": "^2.4.0", @@ -233,6 +234,7 @@ "blocked-at": "^1.2.0", "electron-redux": "2.0.0-alpha.8", "electrum-cash": "^2.0.10", + "humanize-duration": "^3.27.2", "multiaddr": "^10.0.1", "p-queue": "^6.6.2", "path-browserify": "^1.0.1", diff --git a/src/__tests__/store/swapSlice.spec.ts b/src/__tests__/store/swapSlice.spec.ts index cacebd11..5912c509 100644 --- a/src/__tests__/store/swapSlice.spec.ts +++ b/src/__tests__/store/swapSlice.spec.ts @@ -1,5 +1,9 @@ import { AnyAction } from '@reduxjs/toolkit'; -import { SwapSlice, SwapStateBtcLockInMempool, SwapStateType } from '../../models/storeModel'; +import { + SwapSlice, + SwapStateBtcLockInMempool, + SwapStateType, +} from '../../models/storeModel'; import { CliLog, CliLogAliceLockedXmr, @@ -11,10 +15,14 @@ import { CliLogRedeemedXmr, CliLogStartedSwap, CliLogWaitingForBtcDeposit, - SwapSpawnType + SwapSpawnType, } from '../../models/cliModel'; -import reducer, { swapAddLog, swapInitiate, swapProcessExited } from '../../store/features/swapSlice'; +import reducer, { + swapAddLog, + swapInitiate, + swapProcessExited, +} from '../../store/features/swapSlice'; import { Provider } from '../../models/apiModel'; const mWaitingForBtcDepositLog: CliLogWaitingForBtcDeposit = require('../mock_cli_logs/cli_log_waiting_for_bitcoin_deposit.json'); diff --git a/src/main/cli/commands/buyXmrCommand.ts b/src/main/cli/commands/buyXmrCommand.ts index a8bd6d70..0bff2982 100644 --- a/src/main/cli/commands/buyXmrCommand.ts +++ b/src/main/cli/commands/buyXmrCommand.ts @@ -1,5 +1,5 @@ import { dialog } from 'electron'; -import { CliLog } from '../../../models/cliModel'; +import { CliLog, SwapSpawnType } from '../../../models/cliModel'; import { store } from '../../../store/store'; import { swapAddLog, @@ -44,7 +44,7 @@ export async function spawnBuyXmr( store.dispatch( swapInitiate({ provider, - spawnType: 'init', + spawnType: SwapSpawnType.INIT, swapId: null, }) ); @@ -91,7 +91,7 @@ export async function resumeBuyXmr(swapId: string) { store.dispatch( swapInitiate({ provider, - spawnType: 'resume', + spawnType: SwapSpawnType.RESUME, swapId, }) ); diff --git a/src/main/cli/commands/cancelRefundCommand.ts b/src/main/cli/commands/cancelRefundCommand.ts index 1691cf73..2b8a2051 100644 --- a/src/main/cli/commands/cancelRefundCommand.ts +++ b/src/main/cli/commands/cancelRefundCommand.ts @@ -9,7 +9,7 @@ import { import { getCliLogStdOut } from '../dirs'; import { spawnSubcommand } from '../cli'; import logger from '../../../utils/logger'; -import { CliLog } from '../../../models/cliModel'; +import { CliLog, SwapSpawnType } from '../../../models/cliModel'; import spawnBalanceCheck from './balanceCommand'; async function onCliLog(logs: CliLog[]) { @@ -65,16 +65,26 @@ export default async function spawnCancelRefund(swapId: string) { store.dispatch( swapInitiate({ provider, - spawnType: 'cancel-refund', + spawnType: SwapSpawnType.CANCEL_REFUND, swapId, }) ); const stdOut = await getCliLogStdOut(swapId); + let cli = await cancel(async () => { cli = await refund(); }); + cli.stderr.push(stdOut); + + store.dispatch( + swapInitiate({ + provider, + spawnType: SwapSpawnType.CANCEL_REFUND, + swapId, + }) + ); } else { throw new Error('Could not find swap in database'); } diff --git a/src/renderer/components/alert/ConfidentialityAlert.tsx b/src/renderer/components/alert/ConfidentialityAlert.tsx index 75ec1cad..16027f45 100644 --- a/src/renderer/components/alert/ConfidentialityAlert.tsx +++ b/src/renderer/components/alert/ConfidentialityAlert.tsx @@ -4,7 +4,7 @@ export default function ConfidentialityAlert() { return ( This page contains confidential information including private keys. Keep - this information to yourself. Otherwise you will lose your money! + this information to yourself. You will lose your funds if you do not! ); } diff --git a/src/renderer/components/alert/SwapMightBeCancelledAlert.tsx b/src/renderer/components/alert/SwapMightBeCancelledAlert.tsx new file mode 100644 index 00000000..4335d692 --- /dev/null +++ b/src/renderer/components/alert/SwapMightBeCancelledAlert.tsx @@ -0,0 +1,82 @@ +import { makeStyles } from '@material-ui/core'; +import { Alert, AlertTitle } from '@material-ui/lab'; +import { MergedDbState } from '../../../models/databaseModel'; +import { getTimelockStatus } from '../../../utils/parseUtils'; +import HumanizedBitcoinBlockDuration from '../other/HumanizedBitcoinBlockDuration'; + +const useStyles = makeStyles((theme) => ({ + outer: { + marginBottom: theme.spacing(1), + }, + list: { + margin: theme.spacing(0.25), + }, +})); + +export default function SwapMightBeCancelledAlert({ + dbState, + bobBtcLockTxConfirmations, +}: { + dbState: MergedDbState; + bobBtcLockTxConfirmations: number; +}) { + const classes = useStyles(); + + const timelockStatus = getTimelockStatus( + dbState.state.Bob.ExecutionSetupDone.state2.cancel_timelock, + dbState.state.Bob.ExecutionSetupDone.state2.punish_timelock, + bobBtcLockTxConfirmations + ); + + if (bobBtcLockTxConfirmations < 5) { + return <>; + } + + return ( + + Be careful! + The swap provider has taken a long time to lock their Monero. This might + mean that: +
      +
    • They are a malicious actor
    • +
    • + There is a technical issue that prevents them from locking their funds +
    • +
    +
    + There is still hope for the swap to be successful but you have to be extra + careful. Regardless of the reason, it is important that you refund the + swap within the required time period if the swap is unsuccessful. If you + fail to to do so, you will be punished and lose your money. +
      + {timelockStatus.type === 'none' && ( +
    • + + You will be able to refund in about{' '} + + +
    • + )} + {(timelockStatus.type === 'none' || + timelockStatus.type === 'cancelExpired') && ( + +
    • + If you have not refunded or completed the swap in about{' '} + + , you will lose your funds. +
    • +
      + )} +
    • + As long as you see this screen, the swap should be refunded + automatically when the time comes. If it is not you have to manually + refund by navigating to the History page. +
    • +
    +
    + ); +} diff --git a/src/renderer/components/alert/SwapTxLockStatusAlert.tsx b/src/renderer/components/alert/SwapTxLockStatusAlert.tsx index 68ddabe0..b2ce62b2 100644 --- a/src/renderer/components/alert/SwapTxLockStatusAlert.tsx +++ b/src/renderer/components/alert/SwapTxLockStatusAlert.tsx @@ -2,11 +2,8 @@ import { Alert, AlertTitle } from '@material-ui/lab/'; import { useTimelockStatus } from '../../../store/hooks'; import { MergedDbState } from '../../../models/databaseModel'; import { SwapResumeButton } from '../pages/history/table/HistoryRowActions'; -import { - TimelockStatus, - TimelockStatusType, -} from '../../../models/storeModel'; -import { humanizedBitcoinBlockDuration } from '../../../utils/parseUtils'; +import { TimelockStatus, TimelockStatusType } from '../../../models/storeModel'; +import HumanizedBitcoinBlockDuration from '../other/HumanizedBitcoinBlockDuration'; function SwapAlertStatusText({ timelockStatus, @@ -26,12 +23,16 @@ function SwapAlertStatusText({ >
  • You will be able to refund in about{' '} - {humanizedBitcoinBlockDuration(timelockStatus.blocksUntilRefund)} +
  • - If you have not refunded or completed the swap in about{' '} - {humanizedBitcoinBlockDuration(timelockStatus.blocksUntilPunish)}, - you will lose your funds. + You will lose your funds, if you have not refunded or completed + the swap in about{' '} +
  • @@ -40,8 +41,10 @@ function SwapAlertStatusText({ return ( <> Immediately resume the swap! You only have about{' '} - {humanizedBitcoinBlockDuration(timelockStatus.blocksUntilPunish)} left - to refund. After that time has passed, you will lose your funds. + {' '} + left to refund. After that time has passed, you will lose your funds. ); default: diff --git a/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx b/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx index d303bfd5..98d308e6 100644 --- a/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx +++ b/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx @@ -1,6 +1,8 @@ import { Box, DialogContentText } from '@material-ui/core'; import { SwapStateBtcLockInMempool } from '../../../../../../models/storeModel'; -import BitcoinTransactionInfoBox from '../../transaction/BitcoinTransactionInfoBox'; +import BitcoinTransactionInfoBox from '../../BitcoinTransactionInfoBox'; +import { useActiveDbState } from '../../../../../../store/hooks'; +import SwapMightBeCancelledAlert from '../../../../alert/SwapMightBeCancelledAlert'; type BitcoinLockTxInMempoolPageProps = { state: SwapStateBtcLockInMempool; @@ -9,8 +11,16 @@ type BitcoinLockTxInMempoolPageProps = { export default function BitcoinLockTxInMempoolPage({ state, }: BitcoinLockTxInMempoolPageProps) { + const dbState = useActiveDbState(); + return ( + {dbState && ( + + )} The Bitcoin lock transaction has been published. The swap will proceed once the transaction is confirmed and the swap provider locks their diff --git a/src/renderer/components/other/HumanizedBitcoinBlockDuration.tsx b/src/renderer/components/other/HumanizedBitcoinBlockDuration.tsx new file mode 100644 index 00000000..00ed29cc --- /dev/null +++ b/src/renderer/components/other/HumanizedBitcoinBlockDuration.tsx @@ -0,0 +1,17 @@ +import humanizeDuration from 'humanize-duration'; + +const AVG_BLOCK_TIME_MS = 10 * 60 * 1000; + +export default function HumanizedBitcoinBlockDuration({ + blocks, +}: { + blocks: number; +}) { + return ( + <> + {`${humanizeDuration(blocks * AVG_BLOCK_TIME_MS, { + conjunction: ' and ', + })} (${blocks} blocks)`} + + ); +} diff --git a/src/store/features/swapSlice.ts b/src/store/features/swapSlice.ts index 6b2f0971..2439f3b8 100644 --- a/src/store/features/swapSlice.ts +++ b/src/store/features/swapSlice.ts @@ -31,7 +31,8 @@ import { isCliLogStartedSwap, isCliLogWaitingForBtcDeposit, CliLog, - isCliLogAdvancingState, SwapSpawnType + isCliLogAdvancingState, + SwapSpawnType, } from '../../models/cliModel'; import logger from '../../utils/logger'; import { Provider } from '../../models/apiModel'; From 2e5c0a0e98d89bd4f6c759995ab1336f8b14574a Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Thu, 27 Oct 2022 16:37:38 +0200 Subject: [PATCH 07/17] Upgrade swap to 0.11.0 --- .erb/scripts/download-swap-binaries.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.erb/scripts/download-swap-binaries.ts b/.erb/scripts/download-swap-binaries.ts index a6bdaeb9..d8ed2963 100644 --- a/.erb/scripts/download-swap-binaries.ts +++ b/.erb/scripts/download-swap-binaries.ts @@ -7,15 +7,15 @@ const swapBinDir = path.join(__dirname, '../../build/bin/swap'); const binaries = [ { dest: path.join(swapBinDir, 'linux'), - url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/preview/swap_preview_Linux_x86_64.tar', + url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/0.11.0/swap_0.11.0_Linux_x86_64.tar', }, { dest: path.join(swapBinDir, 'mac'), - url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/preview/swap_preview_Darwin_x86_64.tar', + url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/0.11.0/swap_0.11.0_Darwin_x86_64.tar', }, { dest: path.join(swapBinDir, 'win'), - url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/preview/swap_preview_Windows_x86_64.zip', + url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/0.11.0/swap_0.11.0_Windows_x86_64.zip', }, ]; From 308b17007dcc59c39bb4613ce1b7de807429c1a6 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Thu, 27 Oct 2022 16:37:52 +0200 Subject: [PATCH 08/17] Fix ListSellersDialog Title --- src/renderer/components/modal/listSellers/ListSellersDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/modal/listSellers/ListSellersDialog.tsx b/src/renderer/components/modal/listSellers/ListSellersDialog.tsx index 4667ea2c..0cd25f1b 100644 --- a/src/renderer/components/modal/listSellers/ListSellersDialog.tsx +++ b/src/renderer/components/modal/listSellers/ListSellersDialog.tsx @@ -40,7 +40,7 @@ export default function ListSellersDialog({ return ( - Submit a swap provider + Discover swap providers You can manually connect to a rendezvous point to discover swap From cddd28fa6f3bf041f4559bfbe59fd61030cee61a Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Thu, 27 Oct 2022 16:38:26 +0200 Subject: [PATCH 09/17] Display 0 confirmations on History Page for TxLock if no status is known --- .../components/pages/history/table/HistoryRowExpanded.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/pages/history/table/HistoryRowExpanded.tsx b/src/renderer/components/pages/history/table/HistoryRowExpanded.tsx index bb4e9d86..4d7e5598 100644 --- a/src/renderer/components/pages/history/table/HistoryRowExpanded.tsx +++ b/src/renderer/components/pages/history/table/HistoryRowExpanded.tsx @@ -111,7 +111,7 @@ export default function HistoryRowExpanded({ > {txLock.transaction.txid} {' '} - ({txLock.status.confirmations} confirmations) + ({txLock.status.confirmations ?? 0} confirmations) )} From 1b6bf488fb32cc3a622c99598402f47eccf772c8 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Fri, 28 Oct 2022 10:53:37 +0200 Subject: [PATCH 10/17] Update SwapMightBeCancelledAlert.tsx --- src/renderer/components/alert/SwapMightBeCancelledAlert.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/alert/SwapMightBeCancelledAlert.tsx b/src/renderer/components/alert/SwapMightBeCancelledAlert.tsx index 4335d692..0a5ce2e9 100644 --- a/src/renderer/components/alert/SwapMightBeCancelledAlert.tsx +++ b/src/renderer/components/alert/SwapMightBeCancelledAlert.tsx @@ -28,7 +28,7 @@ export default function SwapMightBeCancelledAlert({ bobBtcLockTxConfirmations ); - if (bobBtcLockTxConfirmations < 5) { + if (bobBtcLockTxConfirmations < 3) { return <>; } @@ -46,7 +46,7 @@ export default function SwapMightBeCancelledAlert({
    There is still hope for the swap to be successful but you have to be extra careful. Regardless of the reason, it is important that you refund the - swap within the required time period if the swap is unsuccessful. If you + swap within the required time period if the swap is not completed. If you fail to to do so, you will be punished and lose your money.
      {timelockStatus.type === 'none' && ( From a7b772ab4e80ea534660b48a5c47966f94c9cdcc Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:51:56 +0200 Subject: [PATCH 11/17] Take "Found relevant Bitcoin transaction" log message into account --- src/models/cliModel.ts | 14 ++++++++++++++ src/store/features/swapSlice.ts | 7 +++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/models/cliModel.ts b/src/models/cliModel.ts index 20be9fcb..ac3cd13a 100644 --- a/src/models/cliModel.ts +++ b/src/models/cliModel.ts @@ -92,6 +92,20 @@ export function isCliLogPublishedBtcTx( return log.fields.message === 'Published Bitcoin transaction'; } +export interface CliLogBtcTxFound extends CliLog { + fields: { + message: 'Found relevant Bitcoin transaction'; + txid: string; + status: string; + }; +} + +export function isCliLogBtcTxFound( + log: CliLog +): log is CliLogBtcTxFound { + return log.fields.message === 'Found relevant Bitcoin transaction'; +} + export interface CliLogBtcTxStatusChanged extends CliLog { fields: { message: 'Bitcoin transaction status changed'; diff --git a/src/store/features/swapSlice.ts b/src/store/features/swapSlice.ts index 2439f3b8..bd49f95c 100644 --- a/src/store/features/swapSlice.ts +++ b/src/store/features/swapSlice.ts @@ -33,6 +33,7 @@ import { CliLog, isCliLogAdvancingState, SwapSpawnType, + isCliLogBtcTxFound, } from '../../models/cliModel'; import logger from '../../utils/logger'; import { Provider } from '../../models/apiModel'; @@ -159,10 +160,12 @@ export const swapSlice = createSlice({ slice.state = nextState; } - } else if (isCliLogBtcTxStatusChanged(log)) { + } else if (isCliLogBtcTxStatusChanged(log) || isCliLogBtcTxFound(log)) { if (isSwapStateBtcLockInMempool(slice.state)) { if (slice.state.bobBtcLockTxId === log.fields.txid) { - const newStatusText = log.fields.new_status; + const newStatusText = isCliLogBtcTxStatusChanged(log) + ? log.fields.new_status + : log.fields.status; if (newStatusText.startsWith('confirmed with')) { const confirmations = Number.parseInt( From e9220a91c858e88516c730e18a4a521fa3dce627 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:54:46 +0200 Subject: [PATCH 12/17] Use either confirmations observed by electrum or cli confirmations for BitcoinLockTxInMempoolPage --- .../BitcoinLockTxInMempoolPage.tsx | 10 ++-- src/store/hooks.ts | 46 ++++++++++++++++--- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx b/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx index 98d308e6..4c71a5aa 100644 --- a/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx +++ b/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx @@ -1,7 +1,10 @@ import { Box, DialogContentText } from '@material-ui/core'; import { SwapStateBtcLockInMempool } from '../../../../../../models/storeModel'; import BitcoinTransactionInfoBox from '../../BitcoinTransactionInfoBox'; -import { useActiveDbState } from '../../../../../../store/hooks'; +import { + useActiveDbState, + useMaxTxLockConfirmationsActiveSwap, +} from '../../../../../../store/hooks'; import SwapMightBeCancelledAlert from '../../../../alert/SwapMightBeCancelledAlert'; type BitcoinLockTxInMempoolPageProps = { @@ -12,13 +15,14 @@ export default function BitcoinLockTxInMempoolPage({ state, }: BitcoinLockTxInMempoolPageProps) { const dbState = useActiveDbState(); + const maxTxLockConfirmations = useMaxTxLockConfirmationsActiveSwap(); return ( {dbState && ( )} @@ -34,7 +38,7 @@ export default function BitcoinLockTxInMempoolPage({ <> Most swap providers require 2 confirmations
      - Confirmations: {state.bobBtcLockTxConfirmations} + Confirmations: {maxTxLockConfirmations} } /> diff --git a/src/store/hooks.ts b/src/store/hooks.ts index b3783663..d60d00d8 100644 --- a/src/store/hooks.ts +++ b/src/store/hooks.ts @@ -1,7 +1,11 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; import type { AppDispatch, RootState } from './store'; import { isSwapResumable } from '../models/databaseModel'; -import { TimelockStatus, TimelockStatusType } from '../models/storeModel'; +import { + isSwapStateBtcLockInMempool, + TimelockStatus, + TimelockStatusType, +} from '../models/storeModel'; import { getTimelockStatus } from '../utils/parseUtils'; // Use throughout your app instead of plain `useDispatch` and `useSelector` @@ -26,8 +30,12 @@ export function useDbState(swapId?: string | null) { ); } +export function useActiveSwapId() { + return useAppSelector((s) => s.swap.swapId); +} + export function useActiveDbState() { - const swapId = useAppSelector((s) => s.swap.swapId); + const swapId = useActiveSwapId(); return useDbState(swapId); } @@ -39,21 +47,45 @@ export function useTxLock(swapId: string) { ); } +export function useTxLockConfirmations(swapId: string): number | null { + const txLock = useTxLock(swapId); + + if (txLock == null) { + return null; + } + + // If confirmations is null but we still have a status for the tx, we can assume that the tx is still in the mempool + return txLock.status.confirmations ?? 0; +} + export function useTimelockStatus(swapId: string): TimelockStatus { const dbState = useDbState(swapId); - const txLock = useTxLock(swapId); + const confirmations = useTxLockConfirmations(swapId); - if (dbState == null || txLock == null) { + if (dbState == null || confirmations == null) { return { type: TimelockStatusType.UNKNOWN, }; } - // If confirmations is null but we still have a status for the tx, we can assume that the tx is still in the mempool - const confirmations = txLock.status.confirmations ?? 0; - const { cancel_timelock: refundTimelock, punish_timelock: punishTimelock } = dbState.state.Bob.ExecutionSetupDone.state2; return getTimelockStatus(refundTimelock, punishTimelock, confirmations); } + +export function useMaxTxLockConfirmationsActiveSwap() { + const swapId = useActiveSwapId(); + + const electrumConfirmations = useTxLockConfirmations(swapId ?? '') ?? 0; + + const logConfirmations = useAppSelector((s) => + isSwapStateBtcLockInMempool(s.swap.state) + ? s.swap.state.bobBtcLockTxConfirmations + : 0 + ); + + if (swapId == null) return null; + + return Math.max(electrumConfirmations, logConfirmations); +} From 4d405db3ac3b468ab8285bdbc18817b794a6e110 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Fri, 28 Oct 2022 17:01:43 +0200 Subject: [PATCH 13/17] Always use last stable Tor version --- .erb/scripts/download-tor-binaries.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.erb/scripts/download-tor-binaries.ts b/.erb/scripts/download-tor-binaries.ts index 0bb4c30f..58576bef 100644 --- a/.erb/scripts/download-tor-binaries.ts +++ b/.erb/scripts/download-tor-binaries.ts @@ -2,13 +2,12 @@ import { join } from 'path'; import { TorBrowserRelease, TorDownloader, + TorBrowserBranch, } from '@dreamed-atlas/tor-downloader'; import { emptyDir, ensureDir } from 'fs-extra'; const torBuildDir = join(__dirname, '../../build/bin/tor'); -const TOR_VERSION = '11.5.4'; - const binaries: { platform: NodeJS.Platform; arch: string; @@ -41,8 +40,9 @@ Promise.all( await emptyDir(binary.dest); const downloader = new TorDownloader(); - const release = await TorBrowserRelease.fromValues( - TOR_VERSION, + + const release = await TorBrowserRelease.fromBranch( + TorBrowserBranch.STABLE, binary.platform, binary.arch ); From f79de66063bd840c2de6a3b683041bb40a6549fc Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Sun, 30 Oct 2022 13:59:40 +0100 Subject: [PATCH 14/17] Update `cli` to 0.11.1 --- .erb/scripts/download-swap-binaries.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.erb/scripts/download-swap-binaries.ts b/.erb/scripts/download-swap-binaries.ts index d8ed2963..bedd89e3 100644 --- a/.erb/scripts/download-swap-binaries.ts +++ b/.erb/scripts/download-swap-binaries.ts @@ -7,15 +7,15 @@ const swapBinDir = path.join(__dirname, '../../build/bin/swap'); const binaries = [ { dest: path.join(swapBinDir, 'linux'), - url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/0.11.0/swap_0.11.0_Linux_x86_64.tar', + url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/0.11.1/swap_0.11.1_Linux_x86_64.tar', }, { dest: path.join(swapBinDir, 'mac'), - url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/0.11.0/swap_0.11.0_Darwin_x86_64.tar', + url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/0.11.1/swap_0.11.1_Darwin_x86_64.tar', }, { dest: path.join(swapBinDir, 'win'), - url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/0.11.0/swap_0.11.0_Windows_x86_64.zip', + url: 'https://github.com/comit-network/xmr-btc-swap/releases/download/0.11.1/swap_0.11.1_Windows_x86_64.zip', }, ]; From 92190d6f496d8055d96a1c49ed090706f7d8a257 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Mon, 31 Oct 2022 17:59:56 +0100 Subject: [PATCH 15/17] Remove unnecessary conditions from `isSwapCancellable` and `isSwapRefundable` guards --- src/models/databaseModel.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/models/databaseModel.ts b/src/models/databaseModel.ts index e103b9ff..7aa758fe 100644 --- a/src/models/databaseModel.ts +++ b/src/models/databaseModel.ts @@ -583,6 +583,8 @@ The following conditions must be met: - The bitcoin must be locked - The bitcoin must not be redeemed - The bitcoin must not be cancelled + - The bitcoin must not be refunded + - The bitcoin must not be punished See: https://github.com/comit-network/xmr-btc-swap/blob/7023e75bb51ab26dff4c8fcccdc855d781ca4b15/swap/src/cli/cancel.rs#L16-L35 */ @@ -590,7 +592,9 @@ export function isSwapCancellable(dbState: MergedDbState): boolean { return ( isBtcLockedDbState(dbState.state) && !isBtcRedeemedDbState(dbState.state) && - !isBtcCancelledDbState(dbState.state) + !isBtcCancelledDbState(dbState.state) && + !isDoneBtcRefundedDbState(dbState.state) && + !isDoneBtcPunishedDbState(dbState.state) ); } @@ -599,7 +603,6 @@ Checks if a swap is in a state where it can possibly be refunded (meaning it's n The following conditions must be met: - The bitcoin must be locked - - The bitcoin must be cancelled - The bitcoin must not be redeemed - The bitcoin must not be refunded - The bitcoin must not be punished @@ -609,7 +612,6 @@ See: https://github.com/comit-network/xmr-btc-swap/blob/7023e75bb51ab26dff4c8fcc export function isSwapRefundable(dbState: MergedDbState): boolean { return ( isBtcLockedDbState(dbState.state) && - isBtcCancelledDbState(dbState.state) && !isBtcRedeemedDbState(dbState.state) && !isDoneBtcRefundedDbState(dbState.state) && !isDoneBtcPunishedDbState(dbState.state) From 5f0459cf5b1a8802723d6a3b16ac6230e28a232e Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Tue, 1 Nov 2022 15:14:36 +0100 Subject: [PATCH 16/17] Add ability to add Provider to ProviderList for easier local development via environment variable --- src/models/cliModel.ts | 4 +--- src/store/features/providersSlice.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/models/cliModel.ts b/src/models/cliModel.ts index ac3cd13a..b5f66881 100644 --- a/src/models/cliModel.ts +++ b/src/models/cliModel.ts @@ -100,9 +100,7 @@ export interface CliLogBtcTxFound extends CliLog { }; } -export function isCliLogBtcTxFound( - log: CliLog -): log is CliLogBtcTxFound { +export function isCliLogBtcTxFound(log: CliLog): log is CliLogBtcTxFound { return log.fields.message === 'Found relevant Bitcoin transaction'; } diff --git a/src/store/features/providersSlice.ts b/src/store/features/providersSlice.ts index e3ee840b..50c268df 100644 --- a/src/store/features/providersSlice.ts +++ b/src/store/features/providersSlice.ts @@ -18,6 +18,23 @@ export const providersSlice = createSlice({ initialState, reducers: { setProviders(slice, action: PayloadAction) { + if ( + process.env.STUB_TESTNET_PROVIDER_MULTIADDR && + process.env.STUB_TESTNET_PROVIDER_PEER_ID + ) { + action.payload.push({ + multiAddr: process.env.STUB_TESTNET_PROVIDER_MULTIADDR, + peerId: process.env.STUB_TESTNET_PROVIDER_PEER_ID, + testnet: true, + age: 0, + maxSwapAmount: 10000000, + minSwapAmount: 100000, + price: 700000, + relevancy: 1, + uptime: 1, + }); + } + const providers = sortProviderList(action.payload).filter( (provider) => provider.testnet === isTestnet() ); From 8c6fcc2451aa9ab64470d43173d1d6358a967dfc Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Tue, 1 Nov 2022 16:25:43 +0100 Subject: [PATCH 17/17] Use custom useTxLock hook --- .../pages/in_progress/BitcoinLockTxInMempoolPage.tsx | 2 +- .../pages/history/table/HistoryRowExpanded.tsx | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx b/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx index 4c71a5aa..696f7e67 100644 --- a/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx +++ b/src/renderer/components/modal/swap/pages/in_progress/BitcoinLockTxInMempoolPage.tsx @@ -19,7 +19,7 @@ export default function BitcoinLockTxInMempoolPage({ return ( - {dbState && ( + {dbState && maxTxLockConfirmations != null && ( { - return state.electrum.find( - (tx) => - tx.transaction.swapId === dbState.swapId && - tx.transaction.kind === 'lock' - ); - }); + const txLock = useTxLock(dbState.swapId); return (