Skip to content
This repository has been archived by the owner on Jan 6, 2025. It is now read-only.

Commit

Permalink
Merge branch 'feat/refund-policies-switch' of https://github.com/Unst…
Browse files Browse the repository at this point in the history
…oppableSwap/unstoppableswap-gui into feat/refund-policies-switch
  • Loading branch information
binarybaron committed Jul 25, 2024
2 parents da543c9 + a726c1d commit ef31699
Show file tree
Hide file tree
Showing 16 changed files with 245 additions and 59 deletions.
77 changes: 63 additions & 14 deletions src/models/cliModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,31 +352,80 @@ export function isCliLogDownloadingMoneroWalletRpc(
return log.fields.message === 'Downloading monero-wallet-rpc';
}

export interface CliLogStartedSyncingMoneroWallet extends CliLog {
export interface CliLogGotNotificationForNewBlock extends CliLog {
fields: {
message: 'Syncing Monero wallet';
current_sync_height?: boolean;
message: 'Got notification for new block';
block_height: string;
};
}

export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
export function isCliLogGotNotificationForNewBlock(
log: CliLog,
): log is CliLogGotNotificationForNewBlock {
return log.fields.message === 'Got notification for new block';
}

export interface CliLogAttemptingToCooperativelyRedeemXmr extends CliLog {
fields: {
message: 'Downloading monero-wallet-rpc';
progress: string;
size: string;
download_url: string;
message: 'Attempting to cooperatively redeem XMR after being punished';
};
}

export interface CliLogGotNotificationForNewBlock extends CliLog {
export function isCliLogAttemptingToCooperativelyRedeemXmr(
log: CliLog,
): log is CliLogAttemptingToCooperativelyRedeemXmr {
return (
log.fields.message ===
'Attempting to cooperatively redeem XMR after being punished'
);
}

export interface CliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr
extends CliLog {
fields: {
message: 'Got notification for new block';
block_height: string;
message: 'Alice has accepted our request to cooperatively redeem the XMR';
};
}

export function isCliLogGotNotificationForNewBlock(
export function isCliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr(
log: CliLog,
): log is CliLogGotNotificationForNewBlock {
return log.fields.message === 'Got notification for new block';
): log is CliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr {
return (
log.fields.message ===
'Alice has accepted our request to cooperatively redeem the XMR'
);
}

export interface CliLogAliceRejectedOurRequestForCooperativeXmrRedeem
extends CliLog {
fields: {
message: 'Alice rejected our request for cooperative XMR redeem';
reason: string;
};
}

export function isCliLogAliceRejectedOurRequestForCooperativeXmrRedeem(
log: CliLog,
): log is CliLogAliceRejectedOurRequestForCooperativeXmrRedeem {
return (
log.fields.message ===
'Alice rejected our request for cooperative XMR redeem'
);
}

// tracing::error!(?error, "Failed to request cooperative XMR redeem from Alice");
export interface CliLogFailedToRequestCooperativeXmrRedeemFromAlice
extends CliLog {
fields: {
message: 'Failed to request cooperative XMR redeem from Alice';
error: string;
};
}

export function isCliLogFailedToRequestCooperativeXmrRedeemFromAlice(
log: CliLog,
): log is CliLogFailedToRequestCooperativeXmrRedeemFromAlice {
return (
log.fields.message === 'Failed to request cooperative XMR redeem from Alice'
);
}
34 changes: 34 additions & 0 deletions src/models/storeModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export enum SwapStateType {
BTC_CANCELLED = 'btc cancelled',
BTC_REFUNDED = 'btc refunded',
BTC_PUNISHED = 'btc punished',
ATTEMPTING_COOPERATIVE_REDEEM = 'attempting cooperative redeem',
COOPERATIVE_REDEEM_REJECTED = 'cooperative redeem rejected',
COOPERATIVE_REDEEM_ACCEPTED = 'cooperative redeem accepted',
}

export function isSwapState(state?: SwapState | null): state is SwapState {
Expand Down Expand Up @@ -138,6 +141,37 @@ export function isSwapStateBtcRedemeed(
return state?.type === SwapStateType.BTC_REDEEMED;
}

export interface SwapStateAttemptingCooperativeRedeeem extends SwapState {
type: SwapStateType.ATTEMPTING_COOPERATIVE_REDEEM;
}

export function isSwapStateAttemptingCooperativeRedeeem(
state?: SwapState | null,
): state is SwapStateAttemptingCooperativeRedeeem {
return state?.type === SwapStateType.ATTEMPTING_COOPERATIVE_REDEEM;
}

export interface SwapStateCooperativeRedeemAccepted extends SwapState {
type: SwapStateType.COOPERATIVE_REDEEM_ACCEPTED;
}

export function isSwapStateCooperativeRedeemAccepted(
state?: SwapState | null,
): state is SwapStateCooperativeRedeemAccepted {
return state?.type === SwapStateType.COOPERATIVE_REDEEM_ACCEPTED;
}

export interface SwapStateCooperativeRedeemRejected extends SwapState {
type: SwapStateType.COOPERATIVE_REDEEM_REJECTED;
reason: string;
}

export function isSwapStateCooperativeRedeemRejected(
state?: SwapState | null,
): state is SwapStateCooperativeRedeemRejected {
return state?.type === SwapStateType.COOPERATIVE_REDEEM_REJECTED;
}

export interface SwapStateXmrRedeemInMempool extends SwapState {
type: SwapStateType.XMR_REDEEM_IN_MEMPOOL;
bobXmrRedeemTxId: string;
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/alert/SwapStatusAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export default function SwapStatusAlert({
<Alert
key={swap.swapId}
severity="warning"
action={<SwapResumeButton swap={swap} />}
action={<SwapResumeButton swap={swap}>Resume</SwapResumeButton>}
variant="filled"
>
<AlertTitle>
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/components/alert/UnfinishedSwapsAlert.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Button } from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import { useNavigate } from 'react-router-dom';
import { useResumeableSwapsCount } from 'store/hooks';
import { useResumeableSwapsCountExcludingPunished } from 'store/hooks';

export default function UnfinishedSwapsAlert() {
const resumableSwapsCount = useResumeableSwapsCount();
const resumableSwapsCount = useResumeableSwapsCountExcludingPunished();
const navigate = useNavigate();

if (resumableSwapsCount > 0) {
Expand Down
8 changes: 8 additions & 0 deletions src/renderer/components/modal/provider/ProviderInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
MoneroBitcoinExchangeRate,
SatsAmount,
} from 'renderer/components/other/Units';
import { isProviderOutdated } from 'utils/multiAddrUtils';
import WarningIcon from '@material-ui/icons/Warning';

const useStyles = makeStyles((theme) => ({
content: {
Expand All @@ -28,6 +30,7 @@ export default function ProviderInfo({
provider: ExtendedProviderStatus;
}) {
const classes = useStyles();
const isOutdated = isProviderOutdated(provider);

return (
<Box className={classes.content}>
Expand Down Expand Up @@ -69,6 +72,11 @@ export default function ProviderInfo({
<Chip label="Recommended" icon={<VerifiedUser />} color="primary" />
</Tooltip>
)}
{isOutdated && (
<Tooltip title="This provider is running an outdated version of the software. Outdated providers may be unreliable and cause swaps to take longer to complete or fail entirely.">
<Chip label="Outdated" icon={<WarningIcon />} color="primary" />
</Tooltip>
)}
</Box>
</Box>
);
Expand Down
19 changes: 18 additions & 1 deletion src/renderer/components/modal/swap/pages/SwapStatePage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Box } from '@material-ui/core';
import { useAppSelector } from 'store/hooks';
import {
isSwapStateAttemptingCooperativeRedeeem,
isSwapStateBtcCancelled,
isSwapStateBtcLockInMempool,
isSwapStateBtcPunished,
isSwapStateBtcRedemeed,
isSwapStateBtcRefunded,
isSwapStateCooperativeRedeemAccepted,
isSwapStateCooperativeRedeemRejected,
isSwapStateInitiated,
isSwapStateProcessExited,
isSwapStateReceivedQuote,
Expand All @@ -32,6 +35,7 @@ import BitcoinCancelledPage from './in_progress/BitcoinCancelledPage';
import BitcoinRefundedPage from './done/BitcoinRefundedPage';
import BitcoinPunishedPage from './done/BitcoinPunishedPage';
import { SyncingMoneroWalletPage } from './in_progress/SyncingMoneroWalletPage';
import CircularProgressWithSubtitle from '../CircularProgressWithSubtitle';

export default function SwapStatePage({
swapState,
Expand Down Expand Up @@ -83,7 +87,20 @@ export default function SwapStatePage({
return <BitcoinRefundedPage state={swapState} />;
}
if (isSwapStateBtcPunished(swapState)) {
return <BitcoinPunishedPage />;
return <BitcoinPunishedPage state={null} />;
}
if (isSwapStateAttemptingCooperativeRedeeem(swapState)) {
return (
<CircularProgressWithSubtitle description="Attempting to redeem the Monero with the help of the other party" />
);
}
if (isSwapStateCooperativeRedeemAccepted(swapState)) {
return (
<CircularProgressWithSubtitle description="The other party is cooperating with us to redeem the Monero..." />
);
}
if (isSwapStateCooperativeRedeemRejected(swapState)) {
return <BitcoinPunishedPage state={swapState} />;
}
if (isSwapStateProcessExited(swapState)) {
return <ProcessExitedPage state={swapState} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
import { Box, DialogContentText } from '@material-ui/core';
import FeedbackInfoBox from '../../../../pages/help/FeedbackInfoBox';
import {
SwapStateCooperativeRedeemRejected,
isSwapStateCooperativeRedeemRejected,
} from 'models/storeModel';

export default function BitcoinPunishedPage() {
export default function BitcoinPunishedPage({
state,
}: {
state: null | SwapStateCooperativeRedeemRejected;
}) {
return (
<Box>
<DialogContentText>
Unfortunately, the swap was not successful, and you&apos;ve incurred a
penalty because the swap was not refunded in time. Both the Bitcoin and
Monero are irretrievable.
Unfortunately, the swap was unsuccessful. Since you did not refund in
time, the Bitcoin has been lost. However, with the cooperation of the
other party, you might still be able to redeem the Monero, although this
is not guaranteed.{' '}
{isSwapStateCooperativeRedeemRejected(state) && (
<>
<br />
We tried to redeem the Monero with the other party's help, but it
was unsuccessful (reason: {state.reason}). Attempting again at a
later time might yield success. <br />
</>
)}
</DialogContentText>
<FeedbackInfoBox />
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useActiveSwapInfo } from 'store/hooks';
import { getSwapXmrAmount } from 'models/rpcModel';
import MoneroTransactionInfoBox from '../../MoneroTransactionInfoBox';
import FeedbackInfoBox from '../../../../pages/help/FeedbackInfoBox';
import { MoneroAmount } from 'renderer/components/other/Units';

type XmrRedeemInMempoolPageProps = {
state: SwapStateXmrRedeemInMempool | null;
Expand All @@ -13,11 +14,13 @@ export default function XmrRedeemInMempoolPage({
state,
}: XmrRedeemInMempoolPageProps) {
const swap = useActiveSwapInfo();
const additionalContent = swap
? `This transaction transfers ${getSwapXmrAmount(swap).toFixed(6)} XMR to ${
state?.bobXmrRedeemAddress
}`
: null;
const additionalContent = swap ? (
<>
This transaction transfers{' '}
<MoneroAmount amount={getSwapXmrAmount(swap)} /> to the{' '}
{state?.bobXmrRedeemAddress}
</>
) : null;

return (
<Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SwapStateName } from 'models/rpcModel';
import {
isSwapStateBtcPunished,
isSwapStateBtcRefunded,
isSwapStateCooperativeRedeemRejected,
isSwapStateXmrRedeemInMempool,
SwapStateProcessExited,
} from '../../../../../../models/storeModel';
Expand All @@ -24,7 +25,8 @@ export default function ProcessExitedPage({ state }: ProcessExitedPageProps) {
if (
isSwapStateXmrRedeemInMempool(state.prevState) ||
isSwapStateBtcRefunded(state.prevState) ||
isSwapStateBtcPunished(state.prevState)
isSwapStateBtcPunished(state.prevState) ||
isSwapStateCooperativeRedeemRejected(state.prevState)
) {
return <SwapStatePage swapState={state.prevState} />;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Badge } from '@material-ui/core';
import { useResumeableSwapsCount } from 'store/hooks';
import { useResumeableSwapsCountExcludingPunished } from 'store/hooks';

export default function UnfinishedSwapsBadge({
children,
}: {
children: JSX.Element;
}) {
const resumableSwapsCount = useResumeableSwapsCount();
const resumableSwapsCount = useResumeableSwapsCountExcludingPunished();

if (resumableSwapsCount > 0) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {

export function SwapResumeButton({
swap,
children,
...props
}: { swap: GetSwapInfoResponse } & ButtonProps) {
return (
Expand All @@ -27,7 +28,7 @@ export function SwapResumeButton({
requiresRpc
{...props}
>
Resume
{children}
</IpcInvokeButton>
);
}
Expand Down Expand Up @@ -80,11 +81,11 @@ export default function HistoryRowActions({

if (swap.stateName === SwapStateName.BtcPunished) {
return (
<Tooltip title="The swap is completed because you have been punished">
<ErrorIcon style={{ color: red[500] }} />
<Tooltip title="You have been punished. You can attempt to recover the Monero with the help of the other party but that is not guaranteed to work">
<SwapResumeButton swap={swap}>Attempt recovery</SwapResumeButton>
</Tooltip>
);
}

return <SwapResumeButton swap={swap} />;
return <SwapResumeButton swap={swap}>Resume</SwapResumeButton>;
}
7 changes: 3 additions & 4 deletions src/store/features/providersSlice.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { ExtendedProviderStatus, ProviderStatus } from 'models/apiModel';
import { sortProviderList } from 'utils/sortUtils';
import { isProviderCompatible } from 'utils/multiAddrUtils';
import { isProviderOutdated } from 'utils/multiAddrUtils';
import { getStubTestnetProvider } from 'store/config';

const stubTestnetProvider = getStubTestnetProvider();
Expand Down Expand Up @@ -81,13 +81,12 @@ export const providersSlice = createSlice({
slice,
action: PayloadAction<ExtendedProviderStatus[]>,
) {
// Add stub testnet provider if it exists
if (stubTestnetProvider) {
action.payload.push(stubTestnetProvider);
}

slice.registry.providers = sortProviderList(action.payload).filter(
isProviderCompatible,
);
slice.registry.providers = sortProviderList(action.payload);
slice.selectedProvider = selectNewSelectedProvider(slice);
},
increaseFailedRegistryReconnectAttemptsSinceLastSuccess(slice) {
Expand Down
Loading

0 comments on commit ef31699

Please sign in to comment.