wip: WithdrawDialog migrated to Tauri IPC

This commit is contained in:
binarybaron 2024-08-09 19:46:58 +02:00
parent 47821cbe79
commit 3d16ff6d5c
No known key found for this signature in database
GPG Key ID: 99B75D3E1476A26E
118 changed files with 1779 additions and 1868 deletions

View File

@ -24,5 +24,5 @@ export interface Alert {
id: number;
title: string;
body: string;
severity: 'info' | 'warning' | 'error';
severity: "info" | "warning" | "error";
}

View File

@ -1,14 +1,14 @@
export enum SwapSpawnType {
INIT = 'init',
RESUME = 'resume',
CANCEL_REFUND = 'cancel-refund',
INIT = "init",
RESUME = "resume",
CANCEL_REFUND = "cancel-refund",
}
export type CliLogSpanType = string | 'BitcoinWalletSubscription';
export type CliLogSpanType = string | "BitcoinWalletSubscription";
export interface CliLog {
timestamp: string;
level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'TRACE';
level: "DEBUG" | "INFO" | "WARN" | "ERROR" | "TRACE";
fields: {
message: string;
[index: string]: unknown;
@ -20,12 +20,12 @@ export interface CliLog {
}
export function isCliLog(log: unknown): log is CliLog {
if (log && typeof log === 'object') {
if (log && typeof log === "object") {
return (
'timestamp' in (log as CliLog) &&
'level' in (log as CliLog) &&
'fields' in (log as CliLog) &&
typeof (log as CliLog).fields?.message === 'string'
"timestamp" in (log as CliLog) &&
"level" in (log as CliLog) &&
"fields" in (log as CliLog) &&
typeof (log as CliLog).fields?.message === "string"
);
}
return false;
@ -33,7 +33,7 @@ export function isCliLog(log: unknown): log is CliLog {
export interface CliLogStartedRpcServer extends CliLog {
fields: {
message: 'Started RPC server';
message: "Started RPC server";
addr: string;
};
}
@ -41,12 +41,12 @@ export interface CliLogStartedRpcServer extends CliLog {
export function isCliLogStartedRpcServer(
log: CliLog,
): log is CliLogStartedRpcServer {
return log.fields.message === 'Started RPC server';
return log.fields.message === "Started RPC server";
}
export interface CliLogReleasingSwapLockLog extends CliLog {
fields: {
message: 'Releasing swap lock';
message: "Releasing swap lock";
swap_id: string;
};
}
@ -54,23 +54,23 @@ export interface CliLogReleasingSwapLockLog extends CliLog {
export function isCliLogReleasingSwapLockLog(
log: CliLog,
): log is CliLogReleasingSwapLockLog {
return log.fields.message === 'Releasing swap lock';
return log.fields.message === "Releasing swap lock";
}
export interface CliLogApiCallError extends CliLog {
fields: {
message: 'API call resulted in an error';
message: "API call resulted in an error";
err: string;
};
}
export function isCliLogApiCallError(log: CliLog): log is CliLogApiCallError {
return log.fields.message === 'API call resulted in an error';
return log.fields.message === "API call resulted in an error";
}
export interface CliLogAcquiringSwapLockLog extends CliLog {
fields: {
message: 'Acquiring swap lock';
message: "Acquiring swap lock";
swap_id: string;
};
}
@ -78,12 +78,12 @@ export interface CliLogAcquiringSwapLockLog extends CliLog {
export function isCliLogAcquiringSwapLockLog(
log: CliLog,
): log is CliLogAcquiringSwapLockLog {
return log.fields.message === 'Acquiring swap lock';
return log.fields.message === "Acquiring swap lock";
}
export interface CliLogReceivedQuote extends CliLog {
fields: {
message: 'Received quote';
message: "Received quote";
price: string;
minimum_amount: string;
maximum_amount: string;
@ -91,12 +91,12 @@ export interface CliLogReceivedQuote extends CliLog {
}
export function isCliLogReceivedQuote(log: CliLog): log is CliLogReceivedQuote {
return log.fields.message === 'Received quote';
return log.fields.message === "Received quote";
}
export interface CliLogWaitingForBtcDeposit extends CliLog {
fields: {
message: 'Waiting for Bitcoin deposit';
message: "Waiting for Bitcoin deposit";
deposit_address: string;
min_deposit_until_swap_will_start: string;
max_deposit_until_maximum_amount_is_reached: string;
@ -111,24 +111,24 @@ export interface CliLogWaitingForBtcDeposit extends CliLog {
export function isCliLogWaitingForBtcDeposit(
log: CliLog,
): log is CliLogWaitingForBtcDeposit {
return log.fields.message === 'Waiting for Bitcoin deposit';
return log.fields.message === "Waiting for Bitcoin deposit";
}
export interface CliLogReceivedBtc extends CliLog {
fields: {
message: 'Received Bitcoin';
message: "Received Bitcoin";
max_giveable: string;
new_balance: string;
};
}
export function isCliLogReceivedBtc(log: CliLog): log is CliLogReceivedBtc {
return log.fields.message === 'Received Bitcoin';
return log.fields.message === "Received Bitcoin";
}
export interface CliLogDeterminedSwapAmount extends CliLog {
fields: {
message: 'Determined swap amount';
message: "Determined swap amount";
amount: string;
fees: string;
};
@ -137,49 +137,49 @@ export interface CliLogDeterminedSwapAmount extends CliLog {
export function isCliLogDeterminedSwapAmount(
log: CliLog,
): log is CliLogDeterminedSwapAmount {
return log.fields.message === 'Determined swap amount';
return log.fields.message === "Determined swap amount";
}
export interface CliLogStartedSwap extends CliLog {
fields: {
message: 'Starting new swap';
message: "Starting new swap";
swap_id: string;
};
}
export function isCliLogStartedSwap(log: CliLog): log is CliLogStartedSwap {
return log.fields.message === 'Starting new swap';
return log.fields.message === "Starting new swap";
}
export interface CliLogPublishedBtcTx extends CliLog {
fields: {
message: 'Published Bitcoin transaction';
message: "Published Bitcoin transaction";
txid: string;
kind: 'lock' | 'cancel' | 'withdraw' | 'refund';
kind: "lock" | "cancel" | "withdraw" | "refund";
};
}
export function isCliLogPublishedBtcTx(
log: CliLog,
): log is CliLogPublishedBtcTx {
return log.fields.message === 'Published Bitcoin transaction';
return log.fields.message === "Published Bitcoin transaction";
}
export interface CliLogBtcTxFound extends CliLog {
fields: {
message: 'Found relevant Bitcoin transaction';
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';
return log.fields.message === "Found relevant Bitcoin transaction";
}
export interface CliLogBtcTxStatusChanged extends CliLog {
fields: {
message: 'Bitcoin transaction status changed';
message: "Bitcoin transaction status changed";
txid: string;
new_status: string;
};
@ -188,12 +188,12 @@ export interface CliLogBtcTxStatusChanged extends CliLog {
export function isCliLogBtcTxStatusChanged(
log: CliLog,
): log is CliLogBtcTxStatusChanged {
return log.fields.message === 'Bitcoin transaction status changed';
return log.fields.message === "Bitcoin transaction status changed";
}
export interface CliLogAliceLockedXmr extends CliLog {
fields: {
message: 'Alice locked Monero';
message: "Alice locked Monero";
txid: string;
};
}
@ -201,12 +201,12 @@ export interface CliLogAliceLockedXmr extends CliLog {
export function isCliLogAliceLockedXmr(
log: CliLog,
): log is CliLogAliceLockedXmr {
return log.fields.message === 'Alice locked Monero';
return log.fields.message === "Alice locked Monero";
}
export interface CliLogReceivedXmrLockTxConfirmation extends CliLog {
fields: {
message: 'Received new confirmation for Monero lock tx';
message: "Received new confirmation for Monero lock tx";
txid: string;
seen_confirmations: string;
needed_confirmations: string;
@ -216,50 +216,50 @@ export interface CliLogReceivedXmrLockTxConfirmation extends CliLog {
export function isCliLogReceivedXmrLockTxConfirmation(
log: CliLog,
): log is CliLogReceivedXmrLockTxConfirmation {
return log.fields.message === 'Received new confirmation for Monero lock tx';
return log.fields.message === "Received new confirmation for Monero lock tx";
}
export interface CliLogAdvancingState extends CliLog {
fields: {
message: 'Advancing state';
message: "Advancing state";
state:
| 'quote has been requested'
| 'execution setup done'
| 'btc is locked'
| 'XMR lock transaction transfer proof received'
| 'xmr is locked'
| 'encrypted signature is sent'
| 'btc is redeemed'
| 'cancel timelock is expired'
| 'btc is cancelled'
| 'btc is refunded'
| 'xmr is redeemed'
| 'btc is punished'
| 'safely aborted';
| "quote has been requested"
| "execution setup done"
| "btc is locked"
| "XMR lock transaction transfer proof received"
| "xmr is locked"
| "encrypted signature is sent"
| "btc is redeemed"
| "cancel timelock is expired"
| "btc is cancelled"
| "btc is refunded"
| "xmr is redeemed"
| "btc is punished"
| "safely aborted";
};
}
export function isCliLogAdvancingState(
log: CliLog,
): log is CliLogAdvancingState {
return log.fields.message === 'Advancing state';
return log.fields.message === "Advancing state";
}
export interface CliLogRedeemedXmr extends CliLog {
fields: {
message: 'Successfully transferred XMR to wallet';
message: "Successfully transferred XMR to wallet";
monero_receive_address: string;
txid: string;
};
}
export function isCliLogRedeemedXmr(log: CliLog): log is CliLogRedeemedXmr {
return log.fields.message === 'Successfully transferred XMR to wallet';
return log.fields.message === "Successfully transferred XMR to wallet";
}
export interface YouHaveBeenPunishedCliLog extends CliLog {
fields: {
message: 'You have been punished for not refunding in time';
message: "You have been punished for not refunding in time";
};
}
@ -267,7 +267,7 @@ export function isYouHaveBeenPunishedCliLog(
log: CliLog,
): log is YouHaveBeenPunishedCliLog {
return (
log.fields.message === 'You have been punished for not refunding in time'
log.fields.message === "You have been punished for not refunding in time"
);
}
@ -280,14 +280,14 @@ function getCliLogSpanAttribute<T>(log: CliLog, key: string): T | null {
}
export function getCliLogSpanSwapId(log: CliLog): string | null {
return getCliLogSpanAttribute<string>(log, 'swap_id');
return getCliLogSpanAttribute<string>(log, "swap_id");
}
export function getCliLogSpanLogReferenceId(log: CliLog): string | null {
return (
getCliLogSpanAttribute<string>(log, 'log_reference_id')?.replace(
getCliLogSpanAttribute<string>(log, "log_reference_id")?.replace(
/"/g,
'',
"",
) || null
);
}
@ -301,7 +301,7 @@ export function hasCliLogOneOfMultipleSpans(
export interface CliLogStartedSyncingMoneroWallet extends CliLog {
fields: {
message: 'Syncing Monero wallet';
message: "Syncing Monero wallet";
current_sync_height?: boolean;
};
}
@ -309,18 +309,18 @@ export interface CliLogStartedSyncingMoneroWallet extends CliLog {
export function isCliLogStartedSyncingMoneroWallet(
log: CliLog,
): log is CliLogStartedSyncingMoneroWallet {
return log.fields.message === 'Syncing Monero wallet';
return log.fields.message === "Syncing Monero wallet";
}
export interface CliLogFinishedSyncingMoneroWallet extends CliLog {
fields: {
message: 'Synced Monero wallet';
message: "Synced Monero wallet";
};
}
export interface CliLogFailedToSyncMoneroWallet extends CliLog {
fields: {
message: 'Failed to sync Monero wallet';
message: "Failed to sync Monero wallet";
error: string;
};
}
@ -328,18 +328,18 @@ export interface CliLogFailedToSyncMoneroWallet extends CliLog {
export function isCliLogFailedToSyncMoneroWallet(
log: CliLog,
): log is CliLogFailedToSyncMoneroWallet {
return log.fields.message === 'Failed to sync Monero wallet';
return log.fields.message === "Failed to sync Monero wallet";
}
export function isCliLogFinishedSyncingMoneroWallet(
log: CliLog,
): log is CliLogFinishedSyncingMoneroWallet {
return log.fields.message === 'Monero wallet synced';
return log.fields.message === "Monero wallet synced";
}
export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
fields: {
message: 'Downloading monero-wallet-rpc';
message: "Downloading monero-wallet-rpc";
progress: string;
size: string;
download_url: string;
@ -349,19 +349,19 @@ export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
export function isCliLogDownloadingMoneroWalletRpc(
log: CliLog,
): log is CliLogDownloadingMoneroWalletRpc {
return log.fields.message === 'Downloading monero-wallet-rpc';
return log.fields.message === "Downloading monero-wallet-rpc";
}
export interface CliLogStartedSyncingMoneroWallet extends CliLog {
fields: {
message: 'Syncing Monero wallet';
message: "Syncing Monero wallet";
current_sync_height?: boolean;
};
}
export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
fields: {
message: 'Downloading monero-wallet-rpc';
message: "Downloading monero-wallet-rpc";
progress: string;
size: string;
download_url: string;
@ -370,7 +370,7 @@ export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
export interface CliLogGotNotificationForNewBlock extends CliLog {
fields: {
message: 'Got notification for new block';
message: "Got notification for new block";
block_height: string;
};
}
@ -378,29 +378,36 @@ export interface CliLogGotNotificationForNewBlock extends CliLog {
export function isCliLogGotNotificationForNewBlock(
log: CliLog,
): log is CliLogGotNotificationForNewBlock {
return log.fields.message === 'Got notification for new block';
return log.fields.message === "Got notification for new block";
}
export interface CliLogAttemptingToCooperativelyRedeemXmr extends CliLog {
fields: {
message: 'Attempting to cooperatively redeem XMR after being punished';
message: "Attempting to cooperatively redeem XMR after being punished";
};
}
export function isCliLogAttemptingToCooperativelyRedeemXmr(
log: CliLog,
): log is CliLogAttemptingToCooperativelyRedeemXmr {
return log.fields.message === 'Attempting to cooperatively redeem XMR after being punished';
return (
log.fields.message ===
"Attempting to cooperatively redeem XMR after being punished"
);
}
export interface CliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr extends CliLog {
export interface CliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr
extends CliLog {
fields: {
message: 'Alice has accepted our request to cooperatively redeem the XMR';
message: "Alice has accepted our request to cooperatively redeem the XMR";
};
}
export function isCliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr(
log: CliLog,
): log is CliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr {
return log.fields.message === 'Alice has accepted our request to cooperatively redeem the XMR';
}
return (
log.fields.message ===
"Alice has accepted our request to cooperatively redeem the XMR"
);
}

View File

@ -1,5 +1,5 @@
import { CliLog, SwapSpawnType } from './cliModel';
import { Provider } from './apiModel';
import { CliLog, SwapSpawnType } from "./cliModel";
import { Provider } from "./apiModel";
export interface SwapSlice {
state: SwapState | null;
@ -20,21 +20,21 @@ export interface SwapState {
}
export enum SwapStateType {
INITIATED = 'initiated',
RECEIVED_QUOTE = 'received quote',
WAITING_FOR_BTC_DEPOSIT = 'waiting for btc deposit',
STARTED = 'started',
BTC_LOCK_TX_IN_MEMPOOL = 'btc lock tx is in mempool',
XMR_LOCK_TX_IN_MEMPOOL = 'xmr lock tx is in mempool',
XMR_LOCKED = 'xmr is locked',
BTC_REDEEMED = 'btc redeemed',
XMR_REDEEM_IN_MEMPOOL = 'xmr redeem tx is in mempool',
PROCESS_EXITED = 'process exited',
BTC_CANCELLED = 'btc cancelled',
BTC_REFUNDED = 'btc refunded',
BTC_PUNISHED = 'btc punished',
ATTEMPTING_COOPERATIVE_REDEEM = 'attempting cooperative redeem',
COOPERATIVE_REDEEM_REJECTED = 'cooperative redeem rejected',
INITIATED = "initiated",
RECEIVED_QUOTE = "received quote",
WAITING_FOR_BTC_DEPOSIT = "waiting for btc deposit",
STARTED = "started",
BTC_LOCK_TX_IN_MEMPOOL = "btc lock tx is in mempool",
XMR_LOCK_TX_IN_MEMPOOL = "xmr lock tx is in mempool",
XMR_LOCKED = "xmr is locked",
BTC_REDEEMED = "btc redeemed",
XMR_REDEEM_IN_MEMPOOL = "xmr redeem tx is in mempool",
PROCESS_EXITED = "process exited",
BTC_CANCELLED = "btc cancelled",
BTC_REFUNDED = "btc refunded",
BTC_PUNISHED = "btc punished",
ATTEMPTING_COOPERATIVE_REDEEM = "attempting cooperative redeem",
COOPERATIVE_REDEEM_REJECTED = "cooperative redeem rejected",
}
export function isSwapState(state?: SwapState | null): state is SwapState {

View File

@ -1,6 +1,6 @@
import { Alert, ExtendedProviderStatus } from 'models/apiModel';
import { Alert, ExtendedProviderStatus } from "models/apiModel";
const API_BASE_URL = 'https://api.unstoppableswap.net';
const API_BASE_URL = "https://api.unstoppableswap.net";
export async function fetchProvidersViaHttp(): Promise<
ExtendedProviderStatus[]
@ -23,9 +23,9 @@ export async function submitFeedbackViaHttp(
};
const response = await fetch(`${API_BASE_URL}/api/submit-feedback`, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify({ body, attachedData }),
});
@ -53,9 +53,9 @@ async function fetchCurrencyUsdPrice(currency: string): Promise<number> {
}
export async function fetchBtcPrice(): Promise<number> {
return fetchCurrencyUsdPrice('bitcoin');
return fetchCurrencyUsdPrice("bitcoin");
}
export async function fetchXmrPrice(): Promise<number> {
return fetchCurrencyUsdPrice('monero');
return fetchCurrencyUsdPrice("monero");
}

View File

@ -1,13 +1,13 @@
import { Box, makeStyles, CssBaseline } from '@material-ui/core';
import { createTheme, ThemeProvider } from '@material-ui/core/styles';
import { indigo } from '@material-ui/core/colors';
import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
import Navigation, { drawerWidth } from './navigation/Navigation';
import HistoryPage from './pages/history/HistoryPage';
import SwapPage from './pages/swap/SwapPage';
import WalletPage from './pages/wallet/WalletPage';
import HelpPage from './pages/help/HelpPage';
import GlobalSnackbarProvider from './snackbar/GlobalSnackbarProvider';
import { Box, makeStyles, CssBaseline } from "@material-ui/core";
import { createTheme, ThemeProvider } from "@material-ui/core/styles";
import { indigo } from "@material-ui/core/colors";
import { MemoryRouter as Router, Routes, Route } from "react-router-dom";
import Navigation, { drawerWidth } from "./navigation/Navigation";
import HistoryPage from "./pages/history/HistoryPage";
import SwapPage from "./pages/swap/SwapPage";
import WalletPage from "./pages/wallet/WalletPage";
import HelpPage from "./pages/help/HelpPage";
import GlobalSnackbarProvider from "./snackbar/GlobalSnackbarProvider";
const useStyles = makeStyles((theme) => ({
innerContent: {
@ -20,14 +20,14 @@ const useStyles = makeStyles((theme) => ({
const theme = createTheme({
palette: {
type: 'dark',
type: "dark",
primary: {
main: '#f4511e',
main: "#f4511e",
},
secondary: indigo,
},
transitions: {
create: () => 'none',
create: () => "none",
},
props: {
MuiButtonBase: {

View File

@ -4,12 +4,12 @@ import {
CircularProgress,
IconButton,
Tooltip,
} from '@material-ui/core';
import { ReactElement, ReactNode, useEffect, useState } from 'react';
import { useSnackbar } from 'notistack';
import { useAppSelector } from 'store/hooks';
import { RpcProcessStateType } from 'models/rpcModel';
import { isExternalRpc } from 'store/config';
} from "@material-ui/core";
import { ReactElement, ReactNode, useEffect, useState } from "react";
import { useSnackbar } from "notistack";
import { useAppSelector } from "store/hooks";
import { RpcProcessStateType } from "models/rpcModel";
import { isExternalRpc } from "store/config";
function IpcButtonTooltip({
requiresRpcAndNotReady,
@ -27,19 +27,19 @@ function IpcButtonTooltip({
}
const getMessage = () => {
if (!requiresRpcAndNotReady) return '';
if (!requiresRpcAndNotReady) return "";
switch (processType) {
case RpcProcessStateType.LISTENING_FOR_CONNECTIONS:
return '';
return "";
case RpcProcessStateType.STARTED:
return 'Cannot execute this action because the Swap Daemon is still starting and not yet ready to accept connections. Please wait a moment and try again';
return "Cannot execute this action because the Swap Daemon is still starting and not yet ready to accept connections. Please wait a moment and try again";
case RpcProcessStateType.EXITED:
return 'Cannot execute this action because the Swap Daemon has been stopped. Please start the Swap Daemon again to continue';
return "Cannot execute this action because the Swap Daemon has been stopped. Please start the Swap Daemon again to continue";
case RpcProcessStateType.NOT_STARTED:
return 'Cannot execute this action because the Swap Daemon has not been started yet. Please start the Swap Daemon first';
return "Cannot execute this action because the Swap Daemon has not been started yet. Please start the Swap Daemon first";
default:
return '';
return "";
}
};
@ -108,13 +108,13 @@ export default function IpcInvokeButton<T>({
setIsPending(true);
try {
// const result = await ipcRenderer.invoke(ipcChannel, ...ipcArgs);
throw new Error('Not implemented');
throw new Error("Not implemented");
// onSuccess?.(result);
} catch (e: unknown) {
if (displayErrorSnackbar) {
enqueueSnackbar((e as Error).message, {
autoHideDuration: 60 * 1000,
variant: 'error',
variant: "error",
});
}
} finally {

View File

@ -6,7 +6,7 @@ import { ReactNode, useEffect, useState } from "react";
interface IpcInvokeButtonProps<T> {
onSuccess?: (data: T) => void;
onClick: () => Promise<T>;
onPendingChange?: (bool) => void;
onPendingChange?: (isPending: boolean) => void;
isLoadingOverride?: boolean;
isIconButton?: boolean;
loadIcon?: ReactNode;
@ -46,7 +46,7 @@ export default function PromiseInvokeButton<T>({
onSuccess?.(result);
} catch (e: unknown) {
if (displayErrorSnackbar) {
enqueueSnackbar((e as Error).message, {
enqueueSnackbar(e as String, {
autoHideDuration: 60 * 1000,
variant: "error",
});

View File

@ -1,7 +1,7 @@
import { Button } from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import { useNavigate } from 'react-router-dom';
import { useAppSelector } from 'store/hooks';
import { Button } from "@material-ui/core";
import Alert from "@material-ui/lab/Alert";
import { useNavigate } from "react-router-dom";
import { useAppSelector } from "store/hooks";
export default function FundsLeftInWalletAlert() {
const fundsLeft = useAppSelector((state) => state.rpc.state.balance);
@ -16,7 +16,7 @@ export default function FundsLeftInWalletAlert() {
<Button
color="inherit"
size="small"
onClick={() => navigate('/wallet')}
onClick={() => navigate("/wallet")}
>
View
</Button>

View File

@ -1,6 +1,6 @@
import { Alert } from '@material-ui/lab';
import { Box, LinearProgress } from '@material-ui/core';
import { useAppSelector } from 'store/hooks';
import { Alert } from "@material-ui/lab";
import { Box, LinearProgress } from "@material-ui/core";
import { useAppSelector } from "store/hooks";
export default function MoneroWalletRpcUpdatingAlert() {
const updateState = useAppSelector(
@ -17,7 +17,7 @@ export default function MoneroWalletRpcUpdatingAlert() {
return (
<Alert severity="info">
<Box style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
<Box style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
<span>The Monero wallet is updating. This may take a few moments</span>
<LinearProgress
variant="determinate"

View File

@ -1,8 +1,8 @@
import { Alert } from '@material-ui/lab';
import { Box, makeStyles } from '@material-ui/core';
import { useAppSelector } from 'store/hooks';
import WalletRefreshButton from '../pages/wallet/WalletRefreshButton';
import { SatsAmount } from '../other/Units';
import { Alert } from "@material-ui/lab";
import { Box, makeStyles } from "@material-ui/core";
import { useAppSelector } from "store/hooks";
import WalletRefreshButton from "../pages/wallet/WalletRefreshButton";
import { SatsAmount } from "../other/Units";
const useStyles = makeStyles((theme) => ({
outer: {

View File

@ -1,7 +1,7 @@
import { Alert } from '@material-ui/lab';
import { CircularProgress } from '@material-ui/core';
import { useAppSelector } from 'store/hooks';
import { RpcProcessStateType } from 'models/rpcModel';
import { Alert } from "@material-ui/lab";
import { CircularProgress } from "@material-ui/core";
import { useAppSelector } from "store/hooks";
import { RpcProcessStateType } from "models/rpcModel";
export default function RpcStatusAlert() {
const rpcProcess = useAppSelector((s) => s.rpc.process);

View File

@ -2,102 +2,96 @@ import { makeStyles } from "@material-ui/core";
import { Alert, AlertTitle } from "@material-ui/lab";
import { useActiveSwapInfo } from "store/hooks";
import {
isSwapTimelockInfoCancelled,
isSwapTimelockInfoNone,
isSwapTimelockInfoCancelled,
isSwapTimelockInfoNone,
} from "models/rpcModel";
import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
const useStyles = makeStyles((theme) => ({
outer: {
marginBottom: theme.spacing(1),
},
list: {
margin: theme.spacing(0.25),
},
outer: {
marginBottom: theme.spacing(1),
},
list: {
margin: theme.spacing(0.25),
},
}));
export default function SwapMightBeCancelledAlert({
bobBtcLockTxConfirmations,
bobBtcLockTxConfirmations,
}: {
bobBtcLockTxConfirmations: number;
bobBtcLockTxConfirmations: number;
}) {
const classes = useStyles();
const swap = useActiveSwapInfo();
const classes = useStyles();
const swap = useActiveSwapInfo();
if (
bobBtcLockTxConfirmations < 5 ||
swap === null ||
swap.timelock === null
) {
return <></>;
}
if (
bobBtcLockTxConfirmations < 5 ||
swap === null ||
swap.timelock === null
) {
return <></>;
}
const { timelock } = swap;
const punishTimelockOffset = swap.punish_timelock;
const { timelock } = swap;
const punishTimelockOffset = swap.punish_timelock;
return (
<Alert severity="warning" className={classes.outer} variant="filled">
<AlertTitle>Be careful!</AlertTitle>
The swap provider has taken a long time to lock their Monero. This
might mean that:
<ul className={classes.list}>
<li>
There is a technical issue that prevents them from locking
their funds
</li>
<li>They are a malicious actor (unlikely)</li>
</ul>
<br />
There is still hope for the swap to be successful but you have to be
extra careful. Regardless of why it has taken them so long, it is
important that you refund the 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.
<ul className={classes.list}>
{isSwapTimelockInfoNone(timelock) && (
<>
<li>
<strong>
You will be able to refund in about{" "}
<HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left}
/>
</strong>
</li>
return (
<Alert severity="warning" className={classes.outer} variant="filled">
<AlertTitle>Be careful!</AlertTitle>
The swap provider has taken a long time to lock their Monero. This might
mean that:
<ul className={classes.list}>
<li>
There is a technical issue that prevents them from locking their funds
</li>
<li>They are a malicious actor (unlikely)</li>
</ul>
<br />
There is still hope for the swap to be successful but you have to be extra
careful. Regardless of why it has taken them so long, it is important that
you refund the 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.
<ul className={classes.list}>
{isSwapTimelockInfoNone(timelock) && (
<>
<li>
<strong>
You will be able to refund in about{" "}
<HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left}
/>
</strong>
</li>
<li>
<strong>
If you have not refunded or completed the swap
in about{" "}
<HumanizedBitcoinBlockDuration
blocks={
timelock.None.blocks_left +
punishTimelockOffset
}
/>
, you will lose your funds.
</strong>
</li>
</>
)}
{isSwapTimelockInfoCancelled(timelock) && (
<li>
<strong>
If you have not refunded or completed the swap in
about{" "}
<HumanizedBitcoinBlockDuration
blocks={timelock.Cancel.blocks_left}
/>
, you will lose your funds.
</strong>
</li>
)}
<li>
As long as you see this screen, the swap will be refunded
automatically when the time comes. If this fails, you have
to manually refund by navigating to the History page.
</li>
</ul>
</Alert>
);
<li>
<strong>
If you have not refunded or completed the swap in about{" "}
<HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left + punishTimelockOffset}
/>
, you will lose your funds.
</strong>
</li>
</>
)}
{isSwapTimelockInfoCancelled(timelock) && (
<li>
<strong>
If you have not refunded or completed the swap in about{" "}
<HumanizedBitcoinBlockDuration
blocks={timelock.Cancel.blocks_left}
/>
, you will lose your funds.
</strong>
</li>
)}
<li>
As long as you see this screen, the swap will be refunded
automatically when the time comes. If this fails, you have to manually
refund by navigating to the History page.
</li>
</ul>
</Alert>
);
}

View File

@ -3,33 +3,33 @@ import { Box, makeStyles } from "@material-ui/core";
import { ReactNode } from "react";
import { exhaustiveGuard } from "utils/typescriptUtils";
import {
SwapCancelRefundButton,
SwapResumeButton,
SwapCancelRefundButton,
SwapResumeButton,
} from "../pages/history/table/HistoryRowActions";
import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
import {
GetSwapInfoResponse,
GetSwapInfoResponseRunningSwap,
isGetSwapInfoResponseRunningSwap,
isSwapTimelockInfoCancelled,
isSwapTimelockInfoNone,
isSwapTimelockInfoPunished,
SwapStateName,
SwapTimelockInfoCancelled,
SwapTimelockInfoNone,
GetSwapInfoResponse,
GetSwapInfoResponseRunningSwap,
isGetSwapInfoResponseRunningSwap,
isSwapTimelockInfoCancelled,
isSwapTimelockInfoNone,
isSwapTimelockInfoPunished,
SwapStateName,
SwapTimelockInfoCancelled,
SwapTimelockInfoNone,
} from "../../../models/rpcModel";
import { SwapMoneroRecoveryButton } from "../pages/history/table/SwapMoneroRecoveryButton";
const useStyles = makeStyles({
box: {
display: "flex",
flexDirection: "column",
gap: "0.5rem",
},
list: {
padding: "0px",
margin: "0px",
},
box: {
display: "flex",
flexDirection: "column",
gap: "0.5rem",
},
list: {
padding: "0px",
margin: "0px",
},
});
/**
@ -38,15 +38,15 @@ const useStyles = makeStyles({
* @returns JSX.Element
*/
const MessageList = ({ messages }: { messages: ReactNode[] }) => {
const classes = useStyles();
return (
<ul className={classes.list}>
{messages.map((msg, i) => (
// eslint-disable-next-line react/no-array-index-key
<li key={i}>{msg}</li>
))}
</ul>
);
const classes = useStyles();
return (
<ul className={classes.list}>
{messages.map((msg, i) => (
// eslint-disable-next-line react/no-array-index-key
<li key={i}>{msg}</li>
))}
</ul>
);
};
/**
@ -55,24 +55,20 @@ const MessageList = ({ messages }: { messages: ReactNode[] }) => {
* @returns JSX.Element
*/
const BitcoinRedeemedStateAlert = ({ swap }: { swap: GetSwapInfoResponse }) => {
const classes = useStyles();
return (
<Box className={classes.box}>
<MessageList
messages={[
"The Bitcoin has been redeemed by the other party",
"There is no risk of losing funds. You can take your time",
"The Monero will be automatically redeemed to the address you provided as soon as you resume the swap",
"If this step fails, you can manually redeem the funds",
]}
/>
<SwapMoneroRecoveryButton
swap={swap}
size="small"
variant="contained"
/>
</Box>
);
const classes = useStyles();
return (
<Box className={classes.box}>
<MessageList
messages={[
"The Bitcoin has been redeemed by the other party",
"There is no risk of losing funds. You can take your time",
"The Monero will be automatically redeemed to the address you provided as soon as you resume the swap",
"If this step fails, you can manually redeem the funds",
]}
/>
<SwapMoneroRecoveryButton swap={swap} size="small" variant="contained" />
</Box>
);
};
/**
@ -82,31 +78,28 @@ const BitcoinRedeemedStateAlert = ({ swap }: { swap: GetSwapInfoResponse }) => {
* @returns JSX.Element
*/
const BitcoinLockedNoTimelockExpiredStateAlert = ({
timelock,
punishTimelockOffset,
timelock,
punishTimelockOffset,
}: {
timelock: SwapTimelockInfoNone;
punishTimelockOffset: number;
timelock: SwapTimelockInfoNone;
punishTimelockOffset: number;
}) => (
<MessageList
messages={[
<>
Your Bitcoin is locked. If the swap is not completed in
approximately{" "}
<HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left}
/>
, you need to refund
</>,
<>
You will lose your funds if you do not refund or complete the
swap within{" "}
<HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left + punishTimelockOffset}
/>
</>,
]}
/>
<MessageList
messages={[
<>
Your Bitcoin is locked. If the swap is not completed in approximately{" "}
<HumanizedBitcoinBlockDuration blocks={timelock.None.blocks_left} />,
you need to refund
</>,
<>
You will lose your funds if you do not refund or complete the swap
within{" "}
<HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left + punishTimelockOffset}
/>
</>,
]}
/>
);
/**
@ -117,34 +110,30 @@ const BitcoinLockedNoTimelockExpiredStateAlert = ({
* @returns JSX.Element
*/
const BitcoinPossiblyCancelledAlert = ({
swap,
timelock,
swap,
timelock,
}: {
swap: GetSwapInfoResponse;
timelock: SwapTimelockInfoCancelled;
swap: GetSwapInfoResponse;
timelock: SwapTimelockInfoCancelled;
}) => {
const classes = useStyles();
return (
<Box className={classes.box}>
<MessageList
messages={[
"The swap was cancelled because it did not complete in time",
"You must resume the swap immediately to refund your Bitcoin. If that fails, you can manually refund it",
<>
You will lose your funds if you do not refund within{" "}
<HumanizedBitcoinBlockDuration
blocks={timelock.Cancel.blocks_left}
/>
</>,
]}
const classes = useStyles();
return (
<Box className={classes.box}>
<MessageList
messages={[
"The swap was cancelled because it did not complete in time",
"You must resume the swap immediately to refund your Bitcoin. If that fails, you can manually refund it",
<>
You will lose your funds if you do not refund within{" "}
<HumanizedBitcoinBlockDuration
blocks={timelock.Cancel.blocks_left}
/>
<SwapCancelRefundButton
swap={swap}
size="small"
variant="contained"
/>
</Box>
);
</>,
]}
/>
<SwapCancelRefundButton swap={swap} size="small" variant="contained" />
</Box>
);
};
/**
@ -152,7 +141,7 @@ const BitcoinPossiblyCancelledAlert = ({
* @returns JSX.Element
*/
const ImmediateActionAlert = () => (
<>Resume the swap immediately to avoid losing your funds</>
<>Resume the swap immediately to avoid losing your funds</>
);
/**
@ -161,55 +150,55 @@ const ImmediateActionAlert = () => (
* @returns JSX.Element | null
*/
function SwapAlertStatusText({
swap,
swap,
}: {
swap: GetSwapInfoResponseRunningSwap;
swap: GetSwapInfoResponseRunningSwap;
}) {
switch (swap.state_name) {
// This is the state where the swap is safe because the other party has redeemed the Bitcoin
// It cannot be punished anymore
case SwapStateName.BtcRedeemed:
return <BitcoinRedeemedStateAlert swap={swap} />;
switch (swap.state_name) {
// This is the state where the swap is safe because the other party has redeemed the Bitcoin
// It cannot be punished anymore
case SwapStateName.BtcRedeemed:
return <BitcoinRedeemedStateAlert swap={swap} />;
// These are states that are at risk of punishment because the Bitcoin have been locked
// but has not been redeemed yet by the other party
case SwapStateName.BtcLocked:
case SwapStateName.XmrLockProofReceived:
case SwapStateName.XmrLocked:
case SwapStateName.EncSigSent:
case SwapStateName.CancelTimelockExpired:
case SwapStateName.BtcCancelled:
if (swap.timelock !== null) {
if (isSwapTimelockInfoNone(swap.timelock)) {
return (
<BitcoinLockedNoTimelockExpiredStateAlert
punishTimelockOffset={swap.punish_timelock}
timelock={swap.timelock}
/>
);
}
// These are states that are at risk of punishment because the Bitcoin have been locked
// but has not been redeemed yet by the other party
case SwapStateName.BtcLocked:
case SwapStateName.XmrLockProofReceived:
case SwapStateName.XmrLocked:
case SwapStateName.EncSigSent:
case SwapStateName.CancelTimelockExpired:
case SwapStateName.BtcCancelled:
if (swap.timelock !== null) {
if (isSwapTimelockInfoNone(swap.timelock)) {
return (
<BitcoinLockedNoTimelockExpiredStateAlert
punishTimelockOffset={swap.punish_timelock}
timelock={swap.timelock}
/>
);
}
if (isSwapTimelockInfoCancelled(swap.timelock)) {
return (
<BitcoinPossiblyCancelledAlert
timelock={swap.timelock}
swap={swap}
/>
);
}
if (isSwapTimelockInfoCancelled(swap.timelock)) {
return (
<BitcoinPossiblyCancelledAlert
timelock={swap.timelock}
swap={swap}
/>
);
}
if (isSwapTimelockInfoPunished(swap.timelock)) {
return <ImmediateActionAlert />;
}
if (isSwapTimelockInfoPunished(swap.timelock)) {
return <ImmediateActionAlert />;
}
// We have covered all possible timelock states above
// If we reach this point, it means we have missed a case
return exhaustiveGuard(swap.timelock);
}
return <ImmediateActionAlert />;
default:
return exhaustiveGuard(swap.state_name);
}
// We have covered all possible timelock states above
// If we reach this point, it means we have missed a case
return exhaustiveGuard(swap.timelock);
}
return <ImmediateActionAlert />;
default:
return exhaustiveGuard(swap.state_name);
}
}
/**
@ -218,27 +207,27 @@ function SwapAlertStatusText({
* @returns JSX.Element | null
*/
export default function SwapStatusAlert({
swap,
swap,
}: {
swap: GetSwapInfoResponse;
swap: GetSwapInfoResponse;
}): JSX.Element | null {
// If the swap is not running, there is no need to display the alert
// This is either because the swap is finished or has not started yet (e.g. in the setup phase, no Bitcoin locked)
if (!isGetSwapInfoResponseRunningSwap(swap)) {
return null;
}
// If the swap is not running, there is no need to display the alert
// This is either because the swap is finished or has not started yet (e.g. in the setup phase, no Bitcoin locked)
if (!isGetSwapInfoResponseRunningSwap(swap)) {
return null;
}
return (
<Alert
key={swap.swap_id}
severity="warning"
action={<SwapResumeButton swap={swap} />}
variant="filled"
>
<AlertTitle>
Swap {swap.swap_id.substring(0, 5)}... is unfinished
</AlertTitle>
<SwapAlertStatusText swap={swap} />
</Alert>
);
return (
<Alert
key={swap.swap_id}
severity="warning"
action={<SwapResumeButton swap={swap} />}
variant="filled"
>
<AlertTitle>
Swap {swap.swap_id.substring(0, 5)}... is unfinished
</AlertTitle>
<SwapAlertStatusText swap={swap} />
</Alert>
);
}

View File

@ -3,26 +3,26 @@ import { useSwapInfosSortedByDate } from "store/hooks";
import SwapStatusAlert from "./SwapStatusAlert";
const useStyles = makeStyles((theme) => ({
outer: {
display: "flex",
flexDirection: "column",
gap: theme.spacing(1),
},
outer: {
display: "flex",
flexDirection: "column",
gap: theme.spacing(1),
},
}));
export default function SwapTxLockAlertsBox() {
const classes = useStyles();
const classes = useStyles();
// We specifically choose ALL swaps here
// If a swap is in a state where an Alert is not needed (becaue no Bitcoin have been locked or because the swap has been completed)
// the SwapStatusAlert component will not render an Alert
const swaps = useSwapInfosSortedByDate();
// We specifically choose ALL swaps here
// If a swap is in a state where an Alert is not needed (becaue no Bitcoin have been locked or because the swap has been completed)
// the SwapStatusAlert component will not render an Alert
const swaps = useSwapInfosSortedByDate();
return (
<Box className={classes.outer}>
{swaps.map((swap) => (
<SwapStatusAlert key={swap.swap_id} swap={swap} />
))}
</Box>
);
return (
<Box className={classes.outer}>
{swaps.map((swap) => (
<SwapStatusAlert key={swap.swap_id} swap={swap} />
))}
</Box>
);
}

View File

@ -1,7 +1,7 @@
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 { Button } from "@material-ui/core";
import Alert from "@material-ui/lab/Alert";
import { useNavigate } from "react-router-dom";
import { useResumeableSwapsCount } from "store/hooks";
export default function UnfinishedSwapsAlert() {
const resumableSwapsCount = useResumeableSwapsCount();
@ -16,16 +16,16 @@ export default function UnfinishedSwapsAlert() {
<Button
color="inherit"
size="small"
onClick={() => navigate('/history')}
onClick={() => navigate("/history")}
>
VIEW
</Button>
}
>
You have{' '}
You have{" "}
{resumableSwapsCount > 1
? `${resumableSwapsCount} unfinished swaps`
: 'one unfinished swap'}
: "one unfinished swap"}
</Alert>
);
}

View File

@ -1,5 +1,5 @@
import { SvgIcon } from '@material-ui/core';
import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon';
import { SvgIcon } from "@material-ui/core";
import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
export default function BitcoinIcon(props: SvgIconProps) {
return (

View File

@ -1,5 +1,5 @@
import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon';
import { SvgIcon } from '@material-ui/core';
import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
import { SvgIcon } from "@material-ui/core";
export default function DiscordIcon(props: SvgIconProps) {
return (

View File

@ -1,5 +1,5 @@
import { ReactNode } from 'react';
import { IconButton } from '@material-ui/core';
import { ReactNode } from "react";
import { IconButton } from "@material-ui/core";
export default function LinkIconButton({
url,
@ -9,7 +9,7 @@ export default function LinkIconButton({
children: ReactNode;
}) {
return (
<IconButton component="span" onClick={() => window.open(url, '_blank')}>
<IconButton component="span" onClick={() => window.open(url, "_blank")}>
{children}
</IconButton>
);

View File

@ -1,5 +1,5 @@
import { SvgIcon } from '@material-ui/core';
import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon';
import { SvgIcon } from "@material-ui/core";
import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
export default function MoneroIcon(props: SvgIconProps) {
return (

View File

@ -1,5 +1,5 @@
import { SvgIcon } from '@material-ui/core';
import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon';
import { SvgIcon } from "@material-ui/core";
import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
export default function TorIcon(props: SvgIconProps) {
return (

View File

@ -1,8 +1,8 @@
import { useEffect } from 'react';
import { TextField } from '@material-ui/core';
import { TextFieldProps } from '@material-ui/core/TextField/TextField';
import { isBtcAddressValid } from 'utils/conversionUtils';
import { isTestnet } from 'store/config';
import { useEffect } from "react";
import { TextField } from "@material-ui/core";
import { TextFieldProps } from "@material-ui/core/TextField/TextField";
import { isBtcAddressValid } from "utils/conversionUtils";
import { isTestnet } from "store/config";
export default function BitcoinAddressTextField({
address,
@ -16,11 +16,11 @@ export default function BitcoinAddressTextField({
onAddressValidityChange: (valid: boolean) => void;
helperText: string;
} & TextFieldProps) {
const placeholder = isTestnet() ? 'tb1q4aelwalu...' : 'bc18ociqZ9mZ...';
const placeholder = isTestnet() ? "tb1q4aelwalu..." : "bc18ociqZ9mZ...";
const errorText = isBtcAddressValid(address, isTestnet())
? null
: `Only bech32 addresses are supported. They begin with "${
isTestnet() ? 'tb1' : 'bc1'
isTestnet() ? "tb1" : "bc1"
}"`;
useEffect(() => {

View File

@ -1,8 +1,8 @@
import { useEffect } from 'react';
import { TextField } from '@material-ui/core';
import { TextFieldProps } from '@material-ui/core/TextField/TextField';
import { isXmrAddressValid } from 'utils/conversionUtils';
import { isTestnet } from 'store/config';
import { useEffect } from "react";
import { TextField } from "@material-ui/core";
import { TextFieldProps } from "@material-ui/core/TextField/TextField";
import { isXmrAddressValid } from "utils/conversionUtils";
import { isTestnet } from "store/config";
export default function MoneroAddressTextField({
address,
@ -16,10 +16,10 @@ export default function MoneroAddressTextField({
onAddressValidityChange: (valid: boolean) => void;
helperText: string;
} & TextFieldProps) {
const placeholder = isTestnet() ? '59McWTPGc745...' : '888tNkZrPN6J...';
const placeholder = isTestnet() ? "59McWTPGc745..." : "888tNkZrPN6J...";
const errorText = isXmrAddressValid(address, isTestnet())
? null
: 'Not a valid Monero address';
: "Not a valid Monero address";
useEffect(() => {
onAddressValidityChange(!errorText);

View File

@ -1,9 +1,9 @@
import { DialogTitle, makeStyles, Typography } from '@material-ui/core';
import { DialogTitle, makeStyles, Typography } from "@material-ui/core";
const useStyles = makeStyles({
root: {
display: 'flex',
justifyContent: 'space-between',
display: "flex",
justifyContent: "space-between",
},
});

View File

@ -1,12 +1,12 @@
import { Button, makeStyles, Paper, Typography } from '@material-ui/core';
import { Button, makeStyles, Paper, Typography } from "@material-ui/core";
const useStyles = makeStyles((theme) => ({
logsOuter: {
overflow: 'auto',
overflow: "auto",
padding: theme.spacing(1),
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
maxHeight: '10rem',
maxHeight: "10rem",
},
copyButton: {
marginTop: theme.spacing(1),
@ -17,7 +17,7 @@ export default function PaperTextBox({ stdOut }: { stdOut: string }) {
const classes = useStyles();
function handleCopyLogs() {
throw new Error('Not implemented');
throw new Error("Not implemented");
}
return (

View File

@ -5,8 +5,8 @@ import {
DialogContent,
DialogContentText,
DialogTitle,
} from '@material-ui/core';
import IpcInvokeButton from '../IpcInvokeButton';
} from "@material-ui/core";
import IpcInvokeButton from "../IpcInvokeButton";
type SwapCancelAlertProps = {
open: boolean;

View File

@ -1,14 +1,14 @@
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
MenuItem,
Select,
TextField,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
MenuItem,
Select,
TextField,
} from "@material-ui/core";
import { useState } from "react";
import { useSnackbar } from "notistack";
@ -21,24 +21,24 @@ import { PiconeroAmount } from "../../other/Units";
import LoadingButton from "../../other/LoadingButton";
async function submitFeedback(body: string, swapId: string | number) {
let attachedBody = "";
let attachedBody = "";
if (swapId !== 0 && typeof swapId === "string") {
const swapInfo = store.getState().rpc.state.swapInfos[swapId];
const logs = [] as CliLog[];
if (swapId !== 0 && typeof swapId === "string") {
const swapInfo = store.getState().rpc.state.swapInfos[swapId];
const logs = [] as CliLog[];
throw new Error("Not implemented");
throw new Error("Not implemented");
if (swapInfo === undefined) {
throw new Error(`Swap with id ${swapId} not found`);
}
attachedBody = `${JSON.stringify(swapInfo, null, 4)} \n\nLogs: ${logs
.map((l) => JSON.stringify(l))
.join("\n====\n")}`;
if (swapInfo === undefined) {
throw new Error(`Swap with id ${swapId} not found`);
}
await submitFeedbackViaHttp(body, attachedBody);
attachedBody = `${JSON.stringify(swapInfo, null, 4)} \n\nLogs: ${logs
.map((l) => JSON.stringify(l))
.join("\n====\n")}`;
}
await submitFeedbackViaHttp(body, attachedBody);
}
/*
@ -48,136 +48,126 @@ async function submitFeedback(body: string, swapId: string | number) {
* selectedSwap = 0 means no swap is attached
*/
function SwapSelectDropDown({
selectedSwap,
setSelectedSwap,
selectedSwap,
setSelectedSwap,
}: {
selectedSwap: string | number;
setSelectedSwap: (swapId: string | number) => void;
selectedSwap: string | number;
setSelectedSwap: (swapId: string | number) => void;
}) {
const swaps = useAppSelector((state) =>
Object.values(state.rpc.state.swapInfos),
);
const swaps = useAppSelector((state) =>
Object.values(state.rpc.state.swapInfos),
);
return (
<Select
value={selectedSwap}
label="Attach logs"
variant="outlined"
onChange={(e) => setSelectedSwap(e.target.value as string)}
>
<MenuItem value={0}>Do not attach logs</MenuItem>
{swaps.map((swap) => (
<MenuItem value={swap.swap_id}>
Swap {swap.swap_id.substring(0, 5)}... from{" "}
{new Date(parseDateString(swap.start_date)).toDateString()}{" "}
(
<PiconeroAmount amount={swap.xmr_amount} />)
</MenuItem>
))}
</Select>
);
return (
<Select
value={selectedSwap}
label="Attach logs"
variant="outlined"
onChange={(e) => setSelectedSwap(e.target.value as string)}
>
<MenuItem value={0}>Do not attach logs</MenuItem>
{swaps.map((swap) => (
<MenuItem value={swap.swap_id}>
Swap {swap.swap_id.substring(0, 5)}... from{" "}
{new Date(parseDateString(swap.start_date)).toDateString()} (
<PiconeroAmount amount={swap.xmr_amount} />)
</MenuItem>
))}
</Select>
);
}
const MAX_FEEDBACK_LENGTH = 4000;
export default function FeedbackDialog({
open,
onClose,
open,
onClose,
}: {
open: boolean;
onClose: () => void;
open: boolean;
onClose: () => void;
}) {
const [pending, setPending] = useState(false);
const [bodyText, setBodyText] = useState("");
const currentSwapId = useActiveSwapInfo();
const [pending, setPending] = useState(false);
const [bodyText, setBodyText] = useState("");
const currentSwapId = useActiveSwapInfo();
const { enqueueSnackbar } = useSnackbar();
const { enqueueSnackbar } = useSnackbar();
const [selectedAttachedSwap, setSelectedAttachedSwap] = useState<
string | number
>(currentSwapId?.swap_id || 0);
const [selectedAttachedSwap, setSelectedAttachedSwap] = useState<
string | number
>(currentSwapId?.swap_id || 0);
const bodyTooLong = bodyText.length > MAX_FEEDBACK_LENGTH;
const bodyTooLong = bodyText.length > MAX_FEEDBACK_LENGTH;
return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>Submit Feedback</DialogTitle>
<DialogContent>
<DialogContentText>
Got something to say? Drop us a message below. If you had an
issue with a specific swap, select it from the dropdown to
attach the logs. It will help us figure out what went wrong.
Hit that submit button when you are ready. We appreciate you
taking the time to share your thoughts!
</DialogContentText>
<Box
style={{
display: "flex",
flexDirection: "column",
gap: "1rem",
}}
>
<TextField
variant="outlined"
value={bodyText}
onChange={(e) => setBodyText(e.target.value)}
label={
bodyTooLong
? `Text is too long (${bodyText.length}/${MAX_FEEDBACK_LENGTH})`
: "Feedback"
}
multiline
minRows={4}
maxRows={4}
fullWidth
error={bodyTooLong}
/>
<SwapSelectDropDown
selectedSwap={selectedAttachedSwap}
setSelectedSwap={setSelectedAttachedSwap}
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<LoadingButton
color="primary"
variant="contained"
onClick={async () => {
if (pending) {
return;
}
return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>Submit Feedback</DialogTitle>
<DialogContent>
<DialogContentText>
Got something to say? Drop us a message below. If you had an issue
with a specific swap, select it from the dropdown to attach the logs.
It will help us figure out what went wrong. Hit that submit button
when you are ready. We appreciate you taking the time to share your
thoughts!
</DialogContentText>
<Box
style={{
display: "flex",
flexDirection: "column",
gap: "1rem",
}}
>
<TextField
variant="outlined"
value={bodyText}
onChange={(e) => setBodyText(e.target.value)}
label={
bodyTooLong
? `Text is too long (${bodyText.length}/${MAX_FEEDBACK_LENGTH})`
: "Feedback"
}
multiline
minRows={4}
maxRows={4}
fullWidth
error={bodyTooLong}
/>
<SwapSelectDropDown
selectedSwap={selectedAttachedSwap}
setSelectedSwap={setSelectedAttachedSwap}
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<LoadingButton
color="primary"
variant="contained"
onClick={async () => {
if (pending) {
return;
}
try {
setPending(true);
await submitFeedback(
bodyText,
selectedAttachedSwap,
);
enqueueSnackbar(
"Feedback submitted successfully!",
{
variant: "success",
},
);
} catch (e) {
console.error(`Failed to submit feedback: ${e}`);
enqueueSnackbar(
`Failed to submit feedback (${e})`,
{
variant: "error",
},
);
} finally {
setPending(false);
}
onClose();
}}
loading={pending}
>
Submit
</LoadingButton>
</DialogActions>
</Dialog>
);
try {
setPending(true);
await submitFeedback(bodyText, selectedAttachedSwap);
enqueueSnackbar("Feedback submitted successfully!", {
variant: "success",
});
} catch (e) {
console.error(`Failed to submit feedback: ${e}`);
enqueueSnackbar(`Failed to submit feedback (${e})`, {
variant: "error",
});
} finally {
setPending(false);
}
onClose();
}}
loading={pending}
>
Submit
</LoadingButton>
</DialogActions>
</Dialog>
);
}

View File

@ -1,4 +1,4 @@
import { ChangeEvent, useState } from 'react';
import { ChangeEvent, useState } from "react";
import {
DialogTitle,
Dialog,
@ -11,20 +11,20 @@ import {
Chip,
makeStyles,
Theme,
} from '@material-ui/core';
import { Multiaddr } from 'multiaddr';
import { useSnackbar } from 'notistack';
import IpcInvokeButton from '../../IpcInvokeButton';
} from "@material-ui/core";
import { Multiaddr } from "multiaddr";
import { useSnackbar } from "notistack";
import IpcInvokeButton from "../../IpcInvokeButton";
const PRESET_RENDEZVOUS_POINTS = [
'/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE',
'/dns4/eratosthen.es/tcp/7798/p2p/12D3KooWAh7EXXa2ZyegzLGdjvj1W4G3EXrTGrf6trraoT1MEobs',
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
"/dns4/eratosthen.es/tcp/7798/p2p/12D3KooWAh7EXXa2ZyegzLGdjvj1W4G3EXrTGrf6trraoT1MEobs",
];
const useStyles = makeStyles((theme: Theme) => ({
chipOuter: {
display: 'flex',
flexWrap: 'wrap',
display: "flex",
flexWrap: "wrap",
gap: theme.spacing(1),
},
}));
@ -39,7 +39,7 @@ export default function ListSellersDialog({
onClose,
}: ListSellersDialogProps) {
const classes = useStyles();
const [rendezvousAddress, setRendezvousAddress] = useState('');
const [rendezvousAddress, setRendezvousAddress] = useState("");
const { enqueueSnackbar } = useSnackbar();
function handleMultiAddrChange(event: ChangeEvent<HTMLInputElement>) {
@ -49,12 +49,12 @@ export default function ListSellersDialog({
function getMultiAddressError(): string | null {
try {
const multiAddress = new Multiaddr(rendezvousAddress);
if (!multiAddress.protoNames().includes('p2p')) {
return 'The multi address must contain the peer id (/p2p/)';
if (!multiAddress.protoNames().includes("p2p")) {
return "The multi address must contain the peer id (/p2p/)";
}
return null;
} catch (e) {
return 'Not a valid multi address';
return "Not a valid multi address";
}
}
@ -73,7 +73,7 @@ export default function ListSellersDialog({
}
enqueueSnackbar(message, {
variant: 'success',
variant: "success",
autoHideDuration: 5000,
});
@ -96,7 +96,7 @@ export default function ListSellersDialog({
label="Rendezvous point"
fullWidth
helperText={
getMultiAddressError() || 'Multiaddress of the rendezvous point'
getMultiAddressError() || "Multiaddress of the rendezvous point"
}
value={rendezvousAddress}
onChange={handleMultiAddrChange}

View File

@ -1,24 +1,24 @@
import { makeStyles, Box, Typography, Chip, Tooltip } from '@material-ui/core';
import { VerifiedUser } from '@material-ui/icons';
import { satsToBtc, secondsToDays } from 'utils/conversionUtils';
import { ExtendedProviderStatus } from 'models/apiModel';
import { makeStyles, Box, Typography, Chip, Tooltip } from "@material-ui/core";
import { VerifiedUser } from "@material-ui/icons";
import { satsToBtc, secondsToDays } from "utils/conversionUtils";
import { ExtendedProviderStatus } from "models/apiModel";
import {
MoneroBitcoinExchangeRate,
SatsAmount,
} from 'renderer/components/other/Units';
} from "renderer/components/other/Units";
const useStyles = makeStyles((theme) => ({
content: {
flex: 1,
'& *': {
lineBreak: 'anywhere',
"& *": {
lineBreak: "anywhere",
},
},
chipsOuter: {
display: 'flex',
display: "flex",
marginTop: theme.spacing(1),
gap: theme.spacing(0.5),
flexWrap: 'wrap',
flexWrap: "wrap",
},
}));
@ -41,7 +41,7 @@ export default function ProviderInfo({
{provider.peerId.substring(0, 8)}...{provider.peerId.slice(-8)}
</Typography>
<Typography variant="caption">
Exchange rate:{' '}
Exchange rate:{" "}
<MoneroBitcoinExchangeRate rate={satsToBtc(provider.price)} />
<br />
Minimum swap amount: <SatsAmount amount={provider.minSwapAmount} />
@ -49,7 +49,7 @@ export default function ProviderInfo({
Maximum swap amount: <SatsAmount amount={provider.maxSwapAmount} />
</Typography>
<Box className={classes.chipsOuter}>
<Chip label={provider.testnet ? 'Testnet' : 'Mainnet'} />
<Chip label={provider.testnet ? "Testnet" : "Mainnet"} />
{provider.uptime && (
<Tooltip title="A high uptime indicates reliability. Providers with low uptime may be unreliable and cause swaps to take longer to complete or fail entirely.">
<Chip label={`${Math.round(provider.uptime * 100)} % uptime`} />
@ -58,7 +58,7 @@ export default function ProviderInfo({
{provider.age ? (
<Chip
label={`Went online ${Math.round(secondsToDays(provider.age))} ${
provider.age === 1 ? 'day' : 'days'
provider.age === 1 ? "day" : "days"
} ago`}
/>
) : (

View File

@ -11,21 +11,21 @@ import {
DialogContent,
makeStyles,
CircularProgress,
} from '@material-ui/core';
import AddIcon from '@material-ui/icons/Add';
import { useState } from 'react';
import SearchIcon from '@material-ui/icons/Search';
import { ExtendedProviderStatus } from 'models/apiModel';
} from "@material-ui/core";
import AddIcon from "@material-ui/icons/Add";
import { useState } from "react";
import SearchIcon from "@material-ui/icons/Search";
import { ExtendedProviderStatus } from "models/apiModel";
import {
useAllProviders,
useAppDispatch,
useIsRpcEndpointBusy,
} from 'store/hooks';
import { setSelectedProvider } from 'store/features/providersSlice';
import { RpcMethod } from 'models/rpcModel';
import ProviderSubmitDialog from './ProviderSubmitDialog';
import ListSellersDialog from '../listSellers/ListSellersDialog';
import ProviderInfo from './ProviderInfo';
} from "store/hooks";
import { setSelectedProvider } from "store/features/providersSlice";
import { RpcMethod } from "models/rpcModel";
import ProviderSubmitDialog from "./ProviderSubmitDialog";
import ListSellersDialog from "../listSellers/ListSellersDialog";
import ProviderInfo from "./ProviderInfo";
const useStyles = makeStyles({
dialogContent: {

View File

@ -4,25 +4,25 @@ import {
CardContent,
Box,
IconButton,
} from '@material-ui/core';
import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
import { useState } from 'react';
import { useAppSelector } from 'store/hooks';
import ProviderInfo from './ProviderInfo';
import ProviderListDialog from './ProviderListDialog';
} from "@material-ui/core";
import ArrowForwardIosIcon from "@material-ui/icons/ArrowForwardIos";
import { useState } from "react";
import { useAppSelector } from "store/hooks";
import ProviderInfo from "./ProviderInfo";
import ProviderListDialog from "./ProviderListDialog";
const useStyles = makeStyles({
inner: {
textAlign: 'left',
width: '100%',
height: '100%',
textAlign: "left",
width: "100%",
height: "100%",
},
providerCard: {
width: '100%',
width: "100%",
},
providerCardContent: {
display: 'flex',
alignItems: 'center',
display: "flex",
alignItems: "center",
},
});

View File

@ -1,4 +1,4 @@
import { ChangeEvent, useState } from 'react';
import { ChangeEvent, useState } from "react";
import {
DialogTitle,
Dialog,
@ -7,8 +7,8 @@ import {
TextField,
DialogActions,
Button,
} from '@material-ui/core';
import { Multiaddr } from 'multiaddr';
} from "@material-ui/core";
import { Multiaddr } from "multiaddr";
type ProviderSubmitDialogProps = {
open: boolean;
@ -19,23 +19,23 @@ export default function ProviderSubmitDialog({
open,
onClose,
}: ProviderSubmitDialogProps) {
const [multiAddr, setMultiAddr] = useState('');
const [peerId, setPeerId] = useState('');
const [multiAddr, setMultiAddr] = useState("");
const [peerId, setPeerId] = useState("");
async function handleProviderSubmit() {
if (multiAddr && peerId) {
await fetch('https://api.unstoppableswap.net/api/submit-provider', {
method: 'post',
await fetch("https://api.unstoppableswap.net/api/submit-provider", {
method: "post",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify({
multiAddr,
peerId,
}),
});
setMultiAddr('');
setPeerId('');
setMultiAddr("");
setPeerId("");
onClose();
}
}
@ -51,15 +51,15 @@ export default function ProviderSubmitDialog({
function getMultiAddressError(): string | null {
try {
const multiAddress = new Multiaddr(multiAddr);
if (multiAddress.protoNames().includes('p2p')) {
return 'The multi address should not contain the peer id (/p2p/)';
if (multiAddress.protoNames().includes("p2p")) {
return "The multi address should not contain the peer id (/p2p/)";
}
if (multiAddress.protoNames().find((name) => name.includes('onion'))) {
return 'It is currently not possible to add a provider that is only reachable via Tor';
if (multiAddress.protoNames().find((name) => name.includes("onion"))) {
return "It is currently not possible to add a provider that is only reachable via Tor";
}
return null;
} catch (e) {
return 'Not a valid multi address';
return "Not a valid multi address";
}
}
@ -78,7 +78,7 @@ export default function ProviderSubmitDialog({
fullWidth
helperText={
getMultiAddressError() ||
'Tells the swap client where the provider can be reached'
"Tells the swap client where the provider can be reached"
}
value={multiAddr}
onChange={handleMultiAddrChange}

View File

@ -1,18 +1,18 @@
import QRCode from 'react-qr-code';
import { Box } from '@material-ui/core';
import QRCode from "react-qr-code";
import { Box } from "@material-ui/core";
export default function BitcoinQrCode({ address }: { address: string }) {
return (
<Box
style={{
height: '100%',
margin: '0 auto',
height: "100%",
margin: "0 auto",
}}
>
<QRCode
value={`bitcoin:${address}`}
size={256}
style={{ height: 'auto', maxWidth: '100%', width: '100%' }}
style={{ height: "auto", maxWidth: "100%", width: "100%" }}
/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
/* @ts-ignore */
viewBox="0 0 256 256"

View File

@ -1,8 +1,8 @@
import { isTestnet } from 'store/config';
import { getBitcoinTxExplorerUrl } from 'utils/conversionUtils';
import BitcoinIcon from 'renderer/components/icons/BitcoinIcon';
import { ReactNode } from 'react';
import TransactionInfoBox from './TransactionInfoBox';
import { isTestnet } from "store/config";
import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
import BitcoinIcon from "renderer/components/icons/BitcoinIcon";
import { ReactNode } from "react";
import TransactionInfoBox from "./TransactionInfoBox";
type Props = {
title: string;

View File

@ -3,8 +3,8 @@ import {
CircularProgress,
makeStyles,
Typography,
} from '@material-ui/core';
import { ReactNode } from 'react';
} from "@material-ui/core";
import { ReactNode } from "react";
const useStyles = makeStyles((theme) => ({
subtitle: {

View File

@ -1,12 +1,12 @@
import { Button } from '@material-ui/core';
import { ButtonProps } from '@material-ui/core/Button/Button';
import { Button } from "@material-ui/core";
import { ButtonProps } from "@material-ui/core/Button/Button";
export default function ClipboardIconButton({
text,
...props
}: { text: string } & ButtonProps) {
function writeToClipboard() {
throw new Error('Not implemented');
throw new Error("Not implemented");
}
return (

View File

@ -1,9 +1,9 @@
import { ReactNode } from 'react';
import { Box, Typography } from '@material-ui/core';
import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined';
import InfoBox from './InfoBox';
import ClipboardIconButton from './ClipbiardIconButton';
import BitcoinQrCode from './BitcoinQrCode';
import { ReactNode } from "react";
import { Box, Typography } from "@material-ui/core";
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
import InfoBox from "./InfoBox";
import ClipboardIconButton from "./ClipbiardIconButton";
import BitcoinQrCode from "./BitcoinQrCode";
type Props = {
title: string;
@ -34,10 +34,10 @@ export default function DepositAddressInfoBox({
/>
<Box
style={{
display: 'flex',
flexDirection: 'row',
gap: '0.5rem',
alignItems: 'center',
display: "flex",
flexDirection: "row",
gap: "0.5rem",
alignItems: "center",
}}
>
<Box>{additionalContent}</Box>

View File

@ -4,8 +4,8 @@ import {
makeStyles,
Paper,
Typography,
} from '@material-ui/core';
import { ReactNode } from 'react';
} from "@material-ui/core";
import { ReactNode } from "react";
type Props = {
title: ReactNode;
@ -18,14 +18,14 @@ type Props = {
const useStyles = makeStyles((theme) => ({
outer: {
padding: theme.spacing(1.5),
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
overflow: "hidden",
display: "flex",
flexDirection: "column",
gap: theme.spacing(1),
},
upperContent: {
display: 'flex',
alignItems: 'center',
display: "flex",
alignItems: "center",
gap: theme.spacing(0.5),
},
}));

View File

@ -1,8 +1,8 @@
import { isTestnet } from 'store/config';
import { getMoneroTxExplorerUrl } from 'utils/conversionUtils';
import MoneroIcon from 'renderer/components/icons/MoneroIcon';
import { ReactNode } from 'react';
import TransactionInfoBox from './TransactionInfoBox';
import { isTestnet } from "store/config";
import { getMoneroTxExplorerUrl } from "utils/conversionUtils";
import MoneroIcon from "renderer/components/icons/MoneroIcon";
import { ReactNode } from "react";
import TransactionInfoBox from "./TransactionInfoBox";
type Props = {
title: string;

View File

@ -1,25 +1,25 @@
import { useState } from 'react';
import { useState } from "react";
import {
Button,
Dialog,
DialogActions,
DialogContent,
makeStyles,
} from '@material-ui/core';
import { useAppDispatch, useAppSelector } from 'store/hooks';
import { swapReset } from 'store/features/swapSlice';
import SwapStatePage from './pages/SwapStatePage';
import SwapStateStepper from './SwapStateStepper';
import SwapSuspendAlert from '../SwapSuspendAlert';
import SwapDialogTitle from './SwapDialogTitle';
import DebugPage from './pages/DebugPage';
} from "@material-ui/core";
import { useAppDispatch, useAppSelector } from "store/hooks";
import { swapReset } from "store/features/swapSlice";
import SwapStatePage from "./pages/SwapStatePage";
import SwapStateStepper from "./SwapStateStepper";
import SwapSuspendAlert from "../SwapSuspendAlert";
import SwapDialogTitle from "./SwapDialogTitle";
import DebugPage from "./pages/DebugPage";
const useStyles = makeStyles({
content: {
minHeight: '25rem',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
minHeight: "25rem",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
},
});

View File

@ -1,22 +1,17 @@
import {
Box,
DialogTitle,
makeStyles,
Typography,
} from '@material-ui/core';
import TorStatusBadge from './pages/TorStatusBadge';
import FeedbackSubmitBadge from './pages/FeedbackSubmitBadge';
import DebugPageSwitchBadge from './pages/DebugPageSwitchBadge';
import { Box, DialogTitle, makeStyles, Typography } from "@material-ui/core";
import TorStatusBadge from "./pages/TorStatusBadge";
import FeedbackSubmitBadge from "./pages/FeedbackSubmitBadge";
import DebugPageSwitchBadge from "./pages/DebugPageSwitchBadge";
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
display: "flex",
justifyContent: "space-between",
alignItems: "center",
},
rightSide: {
display: 'flex',
alignItems: 'center',
display: "flex",
alignItems: "center",
gridGap: theme.spacing(1),
},
}));

View File

@ -5,165 +5,162 @@ import { useActiveSwapInfo, useAppSelector } from "store/hooks";
import { exhaustiveGuard } from "utils/typescriptUtils";
export enum PathType {
HAPPY_PATH = "happy path",
UNHAPPY_PATH = "unhappy path",
HAPPY_PATH = "happy path",
UNHAPPY_PATH = "unhappy path",
}
function getActiveStep(
stateName: SwapStateName | null,
processExited: boolean,
stateName: SwapStateName | null,
processExited: boolean,
): [PathType, number, boolean] {
switch (stateName) {
/// // Happy Path
// Step: 0 (Waiting for Bitcoin lock tx to be published)
case null:
return [PathType.HAPPY_PATH, 0, false];
case SwapStateName.Started:
case SwapStateName.SwapSetupCompleted:
return [PathType.HAPPY_PATH, 0, processExited];
switch (stateName) {
/// // Happy Path
// Step: 0 (Waiting for Bitcoin lock tx to be published)
case null:
return [PathType.HAPPY_PATH, 0, false];
case SwapStateName.Started:
case SwapStateName.SwapSetupCompleted:
return [PathType.HAPPY_PATH, 0, processExited];
// Step: 1 (Waiting for Bitcoin Lock confirmation and XMR Lock Publication)
// We have locked the Bitcoin and are waiting for the other party to lock their XMR
case SwapStateName.BtcLocked:
return [PathType.HAPPY_PATH, 1, processExited];
// Step: 1 (Waiting for Bitcoin Lock confirmation and XMR Lock Publication)
// We have locked the Bitcoin and are waiting for the other party to lock their XMR
case SwapStateName.BtcLocked:
return [PathType.HAPPY_PATH, 1, processExited];
// Step: 2 (Waiting for XMR Lock confirmation)
// We have locked the Bitcoin and the other party has locked their XMR
case SwapStateName.XmrLockProofReceived:
return [PathType.HAPPY_PATH, 1, processExited];
// Step: 2 (Waiting for XMR Lock confirmation)
// We have locked the Bitcoin and the other party has locked their XMR
case SwapStateName.XmrLockProofReceived:
return [PathType.HAPPY_PATH, 1, processExited];
// Step: 3 (Sending Encrypted Signature and waiting for Bitcoin Redemption)
// The XMR lock transaction has been confirmed
// We now need to send the encrypted signature to the other party and wait for them to redeem the Bitcoin
case SwapStateName.XmrLocked:
case SwapStateName.EncSigSent:
return [PathType.HAPPY_PATH, 2, processExited];
// Step: 3 (Sending Encrypted Signature and waiting for Bitcoin Redemption)
// The XMR lock transaction has been confirmed
// We now need to send the encrypted signature to the other party and wait for them to redeem the Bitcoin
case SwapStateName.XmrLocked:
case SwapStateName.EncSigSent:
return [PathType.HAPPY_PATH, 2, processExited];
// Step: 4 (Waiting for XMR Redemption)
case SwapStateName.BtcRedeemed:
return [PathType.HAPPY_PATH, 3, processExited];
// Step: 4 (Waiting for XMR Redemption)
case SwapStateName.BtcRedeemed:
return [PathType.HAPPY_PATH, 3, processExited];
// Step: 4 (Completed) (Swap completed, XMR redeemed)
case SwapStateName.XmrRedeemed:
return [PathType.HAPPY_PATH, 4, false];
// Step: 4 (Completed) (Swap completed, XMR redeemed)
case SwapStateName.XmrRedeemed:
return [PathType.HAPPY_PATH, 4, false];
// Edge Case of Happy Path where the swap is safely aborted. We "fail" at the first step.
case SwapStateName.SafelyAborted:
return [PathType.HAPPY_PATH, 0, true];
// Edge Case of Happy Path where the swap is safely aborted. We "fail" at the first step.
case SwapStateName.SafelyAborted:
return [PathType.HAPPY_PATH, 0, true];
// // Unhappy Path
// Step: 1 (Cancelling swap, checking if cancel transaction has been published already by the other party)
case SwapStateName.CancelTimelockExpired:
return [PathType.UNHAPPY_PATH, 0, processExited];
// // Unhappy Path
// Step: 1 (Cancelling swap, checking if cancel transaction has been published already by the other party)
case SwapStateName.CancelTimelockExpired:
return [PathType.UNHAPPY_PATH, 0, processExited];
// Step: 2 (Attempt to publish the Bitcoin refund transaction)
case SwapStateName.BtcCancelled:
return [PathType.UNHAPPY_PATH, 1, processExited];
// Step: 2 (Attempt to publish the Bitcoin refund transaction)
case SwapStateName.BtcCancelled:
return [PathType.UNHAPPY_PATH, 1, processExited];
// Step: 2 (Completed) (Bitcoin refunded)
case SwapStateName.BtcRefunded:
return [PathType.UNHAPPY_PATH, 2, false];
// Step: 2 (Completed) (Bitcoin refunded)
case SwapStateName.BtcRefunded:
return [PathType.UNHAPPY_PATH, 2, false];
// Step: 2 (We failed to publish the Bitcoin refund transaction)
// We failed to publish the Bitcoin refund transaction because the timelock has expired.
// We will be punished. Nothing we can do about it now.
case SwapStateName.BtcPunished:
return [PathType.UNHAPPY_PATH, 1, true];
default:
return exhaustiveGuard(stateName);
}
// Step: 2 (We failed to publish the Bitcoin refund transaction)
// We failed to publish the Bitcoin refund transaction because the timelock has expired.
// We will be punished. Nothing we can do about it now.
case SwapStateName.BtcPunished:
return [PathType.UNHAPPY_PATH, 1, true];
default:
return exhaustiveGuard(stateName);
}
}
function HappyPathStepper({
activeStep,
error,
activeStep,
error,
}: {
activeStep: number;
error: boolean;
activeStep: number;
error: boolean;
}) {
return (
<Stepper activeStep={activeStep}>
<Step key={0}>
<StepLabel
optional={<Typography variant="caption">~12min</Typography>}
error={error && activeStep === 0}
>
Locking your BTC
</StepLabel>
</Step>
<Step key={1}>
<StepLabel
optional={<Typography variant="caption">~18min</Typography>}
error={error && activeStep === 1}
>
They lock their XMR
</StepLabel>
</Step>
<Step key={2}>
<StepLabel
optional={<Typography variant="caption">~2min</Typography>}
error={error && activeStep === 2}
>
They redeem the BTC
</StepLabel>
</Step>
<Step key={3}>
<StepLabel
optional={<Typography variant="caption">~2min</Typography>}
error={error && activeStep === 3}
>
Redeeming your XMR
</StepLabel>
</Step>
</Stepper>
);
return (
<Stepper activeStep={activeStep}>
<Step key={0}>
<StepLabel
optional={<Typography variant="caption">~12min</Typography>}
error={error && activeStep === 0}
>
Locking your BTC
</StepLabel>
</Step>
<Step key={1}>
<StepLabel
optional={<Typography variant="caption">~18min</Typography>}
error={error && activeStep === 1}
>
They lock their XMR
</StepLabel>
</Step>
<Step key={2}>
<StepLabel
optional={<Typography variant="caption">~2min</Typography>}
error={error && activeStep === 2}
>
They redeem the BTC
</StepLabel>
</Step>
<Step key={3}>
<StepLabel
optional={<Typography variant="caption">~2min</Typography>}
error={error && activeStep === 3}
>
Redeeming your XMR
</StepLabel>
</Step>
</Stepper>
);
}
function UnhappyPathStepper({
activeStep,
error,
activeStep,
error,
}: {
activeStep: number;
error: boolean;
activeStep: number;
error: boolean;
}) {
return (
<Stepper activeStep={activeStep}>
<Step key={0}>
<StepLabel
optional={<Typography variant="caption">~20min</Typography>}
error={error && activeStep === 0}
>
Cancelling swap
</StepLabel>
</Step>
<Step key={1}>
<StepLabel
optional={<Typography variant="caption">~20min</Typography>}
error={error && activeStep === 1}
>
Refunding your BTC
</StepLabel>
</Step>
</Stepper>
);
return (
<Stepper activeStep={activeStep}>
<Step key={0}>
<StepLabel
optional={<Typography variant="caption">~20min</Typography>}
error={error && activeStep === 0}
>
Cancelling swap
</StepLabel>
</Step>
<Step key={1}>
<StepLabel
optional={<Typography variant="caption">~20min</Typography>}
error={error && activeStep === 1}
>
Refunding your BTC
</StepLabel>
</Step>
</Stepper>
);
}
export default function SwapStateStepper() {
const currentSwapSpawnType = useAppSelector((s) => s.swap.spawnType);
const stateName = useActiveSwapInfo()?.state_name ?? null;
const processExited = useAppSelector((s) => !s.swap.processRunning);
const [pathType, activeStep, error] = getActiveStep(
stateName,
processExited,
);
const currentSwapSpawnType = useAppSelector((s) => s.swap.spawnType);
const stateName = useActiveSwapInfo()?.state_name ?? null;
const processExited = useAppSelector((s) => !s.swap.processRunning);
const [pathType, activeStep, error] = getActiveStep(stateName, processExited);
// If the current swap is being manually cancelled and refund, we want to show the unhappy path even though the current state is not a "unhappy" state
if (currentSwapSpawnType === SwapSpawnType.CANCEL_REFUND) {
return <UnhappyPathStepper activeStep={0} error={error} />;
}
// If the current swap is being manually cancelled and refund, we want to show the unhappy path even though the current state is not a "unhappy" state
if (currentSwapSpawnType === SwapSpawnType.CANCEL_REFUND) {
return <UnhappyPathStepper activeStep={0} error={error} />;
}
if (pathType === PathType.HAPPY_PATH) {
return <HappyPathStepper activeStep={activeStep} error={error} />;
}
return <UnhappyPathStepper activeStep={activeStep} error={error} />;
if (pathType === PathType.HAPPY_PATH) {
return <HappyPathStepper activeStep={activeStep} error={error} />;
}
return <UnhappyPathStepper activeStep={activeStep} error={error} />;
}

View File

@ -1,6 +1,6 @@
import { Link, Typography } from '@material-ui/core';
import { ReactNode } from 'react';
import InfoBox from './InfoBox';
import { Link, Typography } from "@material-ui/core";
import { ReactNode } from "react";
import InfoBox from "./InfoBox";
type TransactionInfoBoxProps = {
title: string;

View File

@ -1,6 +1,6 @@
import { Tooltip } from '@material-ui/core';
import IconButton from '@material-ui/core/IconButton';
import DeveloperBoardIcon from '@material-ui/icons/DeveloperBoard';
import { Tooltip } from "@material-ui/core";
import IconButton from "@material-ui/core/IconButton";
import DeveloperBoardIcon from "@material-ui/icons/DeveloperBoard";
export default function DebugPageSwitchBadge({
enabled,
@ -14,10 +14,10 @@ export default function DebugPageSwitchBadge({
};
return (
<Tooltip title={enabled ? 'Hide debug view' : 'Show debug view'}>
<Tooltip title={enabled ? "Hide debug view" : "Show debug view"}>
<IconButton
onClick={handleToggle}
color={enabled ? 'primary' : 'default'}
color={enabled ? "primary" : "default"}
>
<DeveloperBoardIcon />
</IconButton>

View File

@ -1,7 +1,7 @@
import { IconButton } from '@material-ui/core';
import FeedbackIcon from '@material-ui/icons/Feedback';
import FeedbackDialog from '../../feedback/FeedbackDialog';
import { useState } from 'react';
import { IconButton } from "@material-ui/core";
import FeedbackIcon from "@material-ui/icons/Feedback";
import FeedbackDialog from "../../feedback/FeedbackDialog";
import { useState } from "react";
export default function FeedbackSubmitBadge() {
const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);

View File

@ -1,5 +1,5 @@
import { Box } from '@material-ui/core';
import { useAppSelector } from 'store/hooks';
import { Box } from "@material-ui/core";
import { useAppSelector } from "store/hooks";
import {
isSwapStateBtcCancelled,
isSwapStateBtcLockInMempool,
@ -15,23 +15,23 @@ import {
isSwapStateXmrLockInMempool,
isSwapStateXmrRedeemInMempool,
SwapState,
} from '../../../../../models/storeModel';
import InitiatedPage from './init/InitiatedPage';
import WaitingForBitcoinDepositPage from './init/WaitingForBitcoinDepositPage';
import StartedPage from './in_progress/StartedPage';
import BitcoinLockTxInMempoolPage from './in_progress/BitcoinLockTxInMempoolPage';
import XmrLockTxInMempoolPage from './in_progress/XmrLockInMempoolPage';
} from "../../../../../models/storeModel";
import InitiatedPage from "./init/InitiatedPage";
import WaitingForBitcoinDepositPage from "./init/WaitingForBitcoinDepositPage";
import StartedPage from "./in_progress/StartedPage";
import BitcoinLockTxInMempoolPage from "./in_progress/BitcoinLockTxInMempoolPage";
import XmrLockTxInMempoolPage from "./in_progress/XmrLockInMempoolPage";
// eslint-disable-next-line import/no-cycle
import ProcessExitedPage from './exited/ProcessExitedPage';
import XmrRedeemInMempoolPage from './done/XmrRedeemInMempoolPage';
import ReceivedQuotePage from './in_progress/ReceivedQuotePage';
import BitcoinRedeemedPage from './in_progress/BitcoinRedeemedPage';
import InitPage from './init/InitPage';
import XmrLockedPage from './in_progress/XmrLockedPage';
import BitcoinCancelledPage from './in_progress/BitcoinCancelledPage';
import BitcoinRefundedPage from './done/BitcoinRefundedPage';
import BitcoinPunishedPage from './done/BitcoinPunishedPage';
import { SyncingMoneroWalletPage } from './in_progress/SyncingMoneroWalletPage';
import ProcessExitedPage from "./exited/ProcessExitedPage";
import XmrRedeemInMempoolPage from "./done/XmrRedeemInMempoolPage";
import ReceivedQuotePage from "./in_progress/ReceivedQuotePage";
import BitcoinRedeemedPage from "./in_progress/BitcoinRedeemedPage";
import InitPage from "./init/InitPage";
import XmrLockedPage from "./in_progress/XmrLockedPage";
import BitcoinCancelledPage from "./in_progress/BitcoinCancelledPage";
import BitcoinRefundedPage from "./done/BitcoinRefundedPage";
import BitcoinPunishedPage from "./done/BitcoinPunishedPage";
import { SyncingMoneroWalletPage } from "./in_progress/SyncingMoneroWalletPage";
export default function SwapStatePage({
swapState,

View File

@ -1,6 +1,6 @@
import { IconButton, Tooltip } from '@material-ui/core';
import { useAppSelector } from 'store/hooks';
import TorIcon from '../../../icons/TorIcon';
import { IconButton, Tooltip } from "@material-ui/core";
import { useAppSelector } from "store/hooks";
import TorIcon from "../../../icons/TorIcon";
export default function TorStatusBadge() {
const tor = useAppSelector((s) => s.tor);

View File

@ -1,5 +1,5 @@
import { Box, DialogContentText } from '@material-ui/core';
import FeedbackInfoBox from '../../../../pages/help/FeedbackInfoBox';
import { Box, DialogContentText } from "@material-ui/core";
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
export default function BitcoinPunishedPage() {
return (

View File

@ -5,40 +5,39 @@ import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
export default function BitcoinRefundedPage({
state,
state,
}: {
state: SwapStateBtcRefunded | null;
state: SwapStateBtcRefunded | null;
}) {
const swap = useActiveSwapInfo();
const additionalContent = swap
? `Refund address: ${swap.btc_refund_address}`
: null;
const swap = useActiveSwapInfo();
const additionalContent = swap
? `Refund address: ${swap.btc_refund_address}`
: null;
return (
<Box>
<DialogContentText>
Unfortunately, the swap was not successful. However, rest
assured that all your Bitcoin has been refunded to the specified
address. The swap process is now complete, and you are free to
exit the application.
</DialogContentText>
<Box
style={{
display: "flex",
flexDirection: "column",
gap: "0.5rem",
}}
>
{state && (
<BitcoinTransactionInfoBox
title="Bitcoin Refund Transaction"
txId={state.bobBtcRefundTxId}
loading={false}
additionalContent={additionalContent}
/>
)}
<FeedbackInfoBox />
</Box>
</Box>
);
return (
<Box>
<DialogContentText>
Unfortunately, the swap was not successful. However, rest assured that
all your Bitcoin has been refunded to the specified address. The swap
process is now complete, and you are free to exit the application.
</DialogContentText>
<Box
style={{
display: "flex",
flexDirection: "column",
gap: "0.5rem",
}}
>
{state && (
<BitcoinTransactionInfoBox
title="Bitcoin Refund Transaction"
txId={state.bobBtcRefundTxId}
loading={false}
additionalContent={additionalContent}
/>
)}
<FeedbackInfoBox />
</Box>
</Box>
);
}

View File

@ -1,9 +1,9 @@
import { Box, DialogContentText } from '@material-ui/core';
import { SwapStateXmrRedeemInMempool } from 'models/storeModel';
import { useActiveSwapInfo } from 'store/hooks';
import { getSwapXmrAmount } from 'models/rpcModel';
import MoneroTransactionInfoBox from '../../MoneroTransactionInfoBox';
import FeedbackInfoBox from '../../../../pages/help/FeedbackInfoBox';
import { Box, DialogContentText } from "@material-ui/core";
import { SwapStateXmrRedeemInMempool } from "models/storeModel";
import { useActiveSwapInfo } from "store/hooks";
import { getSwapXmrAmount } from "models/rpcModel";
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
type XmrRedeemInMempoolPageProps = {
state: SwapStateXmrRedeemInMempool | null;
@ -27,9 +27,9 @@ export default function XmrRedeemInMempoolPage({
</DialogContentText>
<Box
style={{
display: 'flex',
flexDirection: 'column',
gap: '0.5rem',
display: "flex",
flexDirection: "column",
gap: "0.5rem",
}}
>
{state && (

View File

@ -5,67 +5,67 @@ import CliLogsBox from "../../../../other/RenderedCliLog";
import { SwapSpawnType } from "models/cliModel";
export default function ProcessExitedAndNotDonePage({
state,
state,
}: {
state: SwapStateProcessExited;
state: SwapStateProcessExited;
}) {
const swap = useActiveSwapInfo();
const logs = useAppSelector((s) => s.swap.logs);
const spawnType = useAppSelector((s) => s.swap.spawnType);
const swap = useActiveSwapInfo();
const logs = useAppSelector((s) => s.swap.logs);
const spawnType = useAppSelector((s) => s.swap.spawnType);
function getText() {
const isCancelRefund = spawnType === SwapSpawnType.CANCEL_REFUND;
const hasRpcError = state.rpcError != null;
const hasSwap = swap != null;
function getText() {
const isCancelRefund = spawnType === SwapSpawnType.CANCEL_REFUND;
const hasRpcError = state.rpcError != null;
const hasSwap = swap != null;
let messages = [];
let messages = [];
messages.push(
isCancelRefund
? "The manual cancel and refund was unsuccessful."
: "The swap exited unexpectedly without completing.",
);
messages.push(
isCancelRefund
? "The manual cancel and refund was unsuccessful."
: "The swap exited unexpectedly without completing.",
);
if (!hasSwap && !isCancelRefund) {
messages.push("No funds were locked.");
}
messages.push(
hasRpcError
? "Check the error and the logs below for more information."
: "Check the logs below for more information.",
);
if (hasSwap) {
messages.push(`The swap is in the "${swap.state_name}" state.`);
if (!isCancelRefund) {
messages.push(
"Try resuming the swap or attempt to initiate a manual cancel and refund.",
);
}
}
return messages.join(" ");
if (!hasSwap && !isCancelRefund) {
messages.push("No funds were locked.");
}
return (
<Box>
<DialogContentText>{getText()}</DialogContentText>
<Box
style={{
display: "flex",
flexDirection: "column",
gap: "0.5rem",
}}
>
{state.rpcError && (
<CliLogsBox
logs={[state.rpcError]}
label="Error returned by the Swap Daemon"
/>
)}
<CliLogsBox logs={logs} label="Logs relevant to the swap" />
</Box>
</Box>
messages.push(
hasRpcError
? "Check the error and the logs below for more information."
: "Check the logs below for more information.",
);
if (hasSwap) {
messages.push(`The swap is in the "${swap.state_name}" state.`);
if (!isCancelRefund) {
messages.push(
"Try resuming the swap or attempt to initiate a manual cancel and refund.",
);
}
}
return messages.join(" ");
}
return (
<Box>
<DialogContentText>{getText()}</DialogContentText>
<Box
style={{
display: "flex",
flexDirection: "column",
gap: "0.5rem",
}}
>
{state.rpcError && (
<CliLogsBox
logs={[state.rpcError]}
label="Error returned by the Swap Daemon"
/>
)}
<CliLogsBox logs={logs} label="Logs relevant to the swap" />
</Box>
</Box>
);
}

View File

@ -1,10 +1,10 @@
import { useActiveSwapInfo } from "store/hooks";
import { SwapStateName } from "models/rpcModel";
import {
isSwapStateBtcPunished,
isSwapStateBtcRefunded,
isSwapStateXmrRedeemInMempool,
SwapStateProcessExited,
isSwapStateBtcPunished,
isSwapStateBtcRefunded,
isSwapStateXmrRedeemInMempool,
SwapStateProcessExited,
} from "../../../../../../models/storeModel";
import XmrRedeemInMempoolPage from "../done/XmrRedeemInMempoolPage";
import BitcoinPunishedPage from "../done/BitcoinPunishedPage";
@ -14,34 +14,34 @@ import BitcoinRefundedPage from "../done/BitcoinRefundedPage";
import ProcessExitedAndNotDonePage from "./ProcessExitedAndNotDonePage";
type ProcessExitedPageProps = {
state: SwapStateProcessExited;
state: SwapStateProcessExited;
};
export default function ProcessExitedPage({ state }: ProcessExitedPageProps) {
const swap = useActiveSwapInfo();
const swap = useActiveSwapInfo();
// If we have a swap state, for a "done" state we should use it to display additional information that can't be extracted from the database
if (
isSwapStateXmrRedeemInMempool(state.prevState) ||
isSwapStateBtcRefunded(state.prevState) ||
isSwapStateBtcPunished(state.prevState)
) {
return <SwapStatePage swapState={state.prevState} />;
// If we have a swap state, for a "done" state we should use it to display additional information that can't be extracted from the database
if (
isSwapStateXmrRedeemInMempool(state.prevState) ||
isSwapStateBtcRefunded(state.prevState) ||
isSwapStateBtcPunished(state.prevState)
) {
return <SwapStatePage swapState={state.prevState} />;
}
// If we don't have a swap state for a "done" state, we should fall back to using the database to display as much information as we can
if (swap) {
if (swap.state_name === SwapStateName.XmrRedeemed) {
return <XmrRedeemInMempoolPage state={null} />;
}
// If we don't have a swap state for a "done" state, we should fall back to using the database to display as much information as we can
if (swap) {
if (swap.state_name === SwapStateName.XmrRedeemed) {
return <XmrRedeemInMempoolPage state={null} />;
}
if (swap.state_name === SwapStateName.BtcRefunded) {
return <BitcoinRefundedPage state={null} />;
}
if (swap.state_name === SwapStateName.BtcPunished) {
return <BitcoinPunishedPage />;
}
if (swap.state_name === SwapStateName.BtcRefunded) {
return <BitcoinRefundedPage state={null} />;
}
if (swap.state_name === SwapStateName.BtcPunished) {
return <BitcoinPunishedPage />;
}
}
// If the swap is not a "done" state (or we don't have a db state because the swap did complete the SwapSetup yet) we should tell the user and show logs
return <ProcessExitedAndNotDonePage state={state} />;
// If the swap is not a "done" state (or we don't have a db state because the swap did complete the SwapSetup yet) we should tell the user and show logs
return <ProcessExitedAndNotDonePage state={state} />;
}

View File

@ -1,4 +1,4 @@
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
export default function BitcoinCancelledPage() {
return <CircularProgressWithSubtitle description="Refunding your Bitcoin" />;

View File

@ -1,7 +1,7 @@
import { Box, DialogContentText } from '@material-ui/core';
import { SwapStateBtcLockInMempool } from 'models/storeModel';
import BitcoinTransactionInfoBox from '../../BitcoinTransactionInfoBox';
import SwapMightBeCancelledAlert from '../../../../alert/SwapMightBeCancelledAlert';
import { Box, DialogContentText } from "@material-ui/core";
import { SwapStateBtcLockInMempool } from "models/storeModel";
import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
import SwapMightBeCancelledAlert from "../../../../alert/SwapMightBeCancelledAlert";
type BitcoinLockTxInMempoolPageProps = {
state: SwapStateBtcLockInMempool;

View File

@ -1,4 +1,4 @@
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
export default function BitcoinRedeemedPage() {
return <CircularProgressWithSubtitle description="Redeeming your Monero" />;

View File

@ -1,4 +1,4 @@
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
export default function ReceivedQuotePage() {
return (

View File

@ -1,6 +1,6 @@
import { SwapStateStarted } from 'models/storeModel';
import { BitcoinAmount } from 'renderer/components/other/Units';
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
import { SwapStateStarted } from "models/storeModel";
import { BitcoinAmount } from "renderer/components/other/Units";
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
export default function StartedPage({ state }: { state: SwapStateStarted }) {
const description = state.txLockDetails ? (
@ -9,7 +9,7 @@ export default function StartedPage({ state }: { state: SwapStateStarted }) {
network fee of <BitcoinAmount amount={state.txLockDetails.fees} />
</>
) : (
'Locking Bitcoin'
"Locking Bitcoin"
);
return <CircularProgressWithSubtitle description={description} />;

View File

@ -1,4 +1,4 @@
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
export function SyncingMoneroWalletPage() {
return (

View File

@ -1,6 +1,6 @@
import { Box, DialogContentText } from '@material-ui/core';
import { SwapStateXmrLockInMempool } from 'models/storeModel';
import MoneroTransactionInfoBox from '../../MoneroTransactionInfoBox';
import { Box, DialogContentText } from "@material-ui/core";
import { SwapStateXmrLockInMempool } from "models/storeModel";
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
type XmrLockTxInMempoolPageProps = {
state: SwapStateXmrLockInMempool;

View File

@ -1,4 +1,4 @@
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
export default function XmrLockedPage() {
return (

View File

@ -1,24 +1,24 @@
import { useState } from 'react';
import { Box, makeStyles, TextField, Typography } from '@material-ui/core';
import { SwapStateWaitingForBtcDeposit } from 'models/storeModel';
import { useAppSelector } from 'store/hooks';
import { satsToBtc } from 'utils/conversionUtils';
import { MoneroAmount } from '../../../../other/Units';
import { useState } from "react";
import { Box, makeStyles, TextField, Typography } from "@material-ui/core";
import { SwapStateWaitingForBtcDeposit } from "models/storeModel";
import { useAppSelector } from "store/hooks";
import { satsToBtc } from "utils/conversionUtils";
import { MoneroAmount } from "../../../../other/Units";
const MONERO_FEE = 0.000016;
const useStyles = makeStyles((theme) => ({
outer: {
display: 'flex',
alignItems: 'center',
display: "flex",
alignItems: "center",
gap: theme.spacing(1),
},
textField: {
'& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button': {
display: 'none',
"& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button": {
display: "none",
},
'& input[type=number]': {
MozAppearance: 'textfield',
"& input[type=number]": {
MozAppearance: "textfield",
},
maxWidth: theme.spacing(16),
},
@ -83,7 +83,7 @@ export default function DepositAmountHelper({
className={classes.textField}
/>
<Typography variant="subtitle2">
BTC will give you approximately{' '}
BTC will give you approximately{" "}
<MoneroAmount amount={calcXMRAmount()} />.
</Typography>
</Box>

View File

@ -1,5 +1,5 @@
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
import { MoneroWalletRpcUpdateState } from '../../../../../../models/storeModel';
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
import { MoneroWalletRpcUpdateState } from "../../../../../../models/storeModel";
export default function DownloadingMoneroWalletRpcPage({
updateState,

View File

@ -1,32 +1,28 @@
import { Box, DialogContentText, makeStyles } from '@material-ui/core';
import { useState } from 'react';
import BitcoinAddressTextField from 'renderer/components/inputs/BitcoinAddressTextField';
import MoneroAddressTextField from 'renderer/components/inputs/MoneroAddressTextField';
import { useAppSelector } from 'store/hooks';
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
import { isTestnet } from 'store/config';
import RemainingFundsWillBeUsedAlert from '../../../../alert/RemainingFundsWillBeUsedAlert';
import IpcInvokeButton from '../../../../IpcInvokeButton';
import { Box, DialogContentText, makeStyles } from "@material-ui/core";
import { useState } from "react";
import BitcoinAddressTextField from "renderer/components/inputs/BitcoinAddressTextField";
import MoneroAddressTextField from "renderer/components/inputs/MoneroAddressTextField";
import { useAppSelector } from "store/hooks";
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
import { isTestnet } from "store/config";
import RemainingFundsWillBeUsedAlert from "../../../../alert/RemainingFundsWillBeUsedAlert";
import IpcInvokeButton from "../../../../IpcInvokeButton";
const useStyles = makeStyles((theme) => ({
initButton: {
marginTop: theme.spacing(1),
},
fieldsOuter: {
display: 'flex',
flexDirection: 'column',
display: "flex",
flexDirection: "column",
gap: theme.spacing(2),
},
}));
export default function InitPage() {
const classes = useStyles();
const [redeemAddress, setRedeemAddress] = useState(
''
);
const [refundAddress, setRefundAddress] = useState(
''
);
const [redeemAddress, setRedeemAddress] = useState("");
const [refundAddress, setRefundAddress] = useState("");
const [redeemAddressValid, setRedeemAddressValid] = useState(false);
const [refundAddressValid, setRefundAddressValid] = useState(false);
const selectedProvider = useAppSelector(

View File

@ -1,19 +1,19 @@
import { useAppSelector } from 'store/hooks';
import { SwapSpawnType } from 'models/cliModel';
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
import { useAppSelector } from "store/hooks";
import { SwapSpawnType } from "models/cliModel";
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
export default function InitiatedPage() {
const description = useAppSelector((s) => {
switch (s.swap.spawnType) {
case SwapSpawnType.INIT:
return 'Requesting quote from provider...';
return "Requesting quote from provider...";
case SwapSpawnType.RESUME:
return 'Resuming swap...';
return "Resuming swap...";
case SwapSpawnType.CANCEL_REFUND:
return 'Attempting to cancel & refund swap...';
return "Attempting to cancel & refund swap...";
default:
// Should never be hit
return 'Initiating swap...';
return "Initiating swap...";
}
});

View File

@ -1,25 +1,25 @@
import { Box, makeStyles, Typography } from '@material-ui/core';
import { SwapStateWaitingForBtcDeposit } from 'models/storeModel';
import { useAppSelector } from 'store/hooks';
import DepositAddressInfoBox from '../../DepositAddressInfoBox';
import BitcoinIcon from '../../../../icons/BitcoinIcon';
import DepositAmountHelper from './DepositAmountHelper';
import { Box, makeStyles, Typography } from "@material-ui/core";
import { SwapStateWaitingForBtcDeposit } from "models/storeModel";
import { useAppSelector } from "store/hooks";
import DepositAddressInfoBox from "../../DepositAddressInfoBox";
import BitcoinIcon from "../../../../icons/BitcoinIcon";
import DepositAmountHelper from "./DepositAmountHelper";
import {
BitcoinAmount,
MoneroBitcoinExchangeRate,
SatsAmount,
} from '../../../../other/Units';
} from "../../../../other/Units";
const useStyles = makeStyles((theme) => ({
amountHelper: {
display: 'flex',
alignItems: 'center',
display: "flex",
alignItems: "center",
},
additionalContent: {
paddingTop: theme.spacing(1),
gap: theme.spacing(0.5),
display: 'flex',
flexDirection: 'column',
display: "flex",
flexDirection: "column",
},
}));
@ -45,13 +45,13 @@ export default function WaitingForBtcDepositPage({
<ul>
{bitcoinBalance > 0 ? (
<li>
You have already deposited{' '}
You have already deposited{" "}
<SatsAmount amount={bitcoinBalance} />
</li>
) : null}
<li>
Send any amount between{' '}
<BitcoinAmount amount={state.minDeposit} /> and{' '}
Send any amount between{" "}
<BitcoinAmount amount={state.minDeposit} /> and{" "}
<BitcoinAmount amount={state.maxDeposit} /> to the address
above
{bitcoinBalance > 0 && (
@ -60,11 +60,11 @@ export default function WaitingForBtcDepositPage({
</li>
<li>
All Bitcoin sent to this this address will converted into
Monero at an exchance rate of{' '}
Monero at an exchance rate of{" "}
<MoneroBitcoinExchangeRate rate={state.price} />
</li>
<li>
The network fee of{' '}
The network fee of{" "}
<BitcoinAmount amount={state.minBitcoinLockTxFee} /> will
automatically be deducted from the deposited coins
</li>
@ -74,9 +74,7 @@ export default function WaitingForBtcDepositPage({
</li>
</ul>
</Typography>
<DepositAmountHelper
state={state}
/>
<DepositAmountHelper state={state} />
</Box>
}
icon={<BitcoinIcon />}

View File

@ -9,6 +9,7 @@ import { useState } from "react";
import { withdrawBtc } from "renderer/rpc";
import BtcTxInMempoolPageContent from "./pages/BitcoinWithdrawTxInMempoolPage";
import AddressInputPage from "./pages/AddressInputPage";
import WithdrawDialogContent from "./WithdrawDialogContent";
export default function WithdrawDialog({
open,
@ -30,27 +31,30 @@ export default function WithdrawDialog({
}
}
// This prevents an issue where the Dialog is shown for a split second without a present withdraw state
if (!open) return null;
return (
<Dialog open onClose={onCancel} maxWidth="sm" fullWidth>
<Dialog open={open} onClose={onCancel} maxWidth="sm" fullWidth>
<DialogHeader title="Withdraw Bitcoin" />
{withdrawTxId === null ? (
<AddressInputPage
setWithdrawAddress={setWithdrawAddress}
withdrawAddress={withdrawAddress}
setWithdrawAddressValid={setWithdrawAddressValid}
/>
) : (
<BtcTxInMempoolPageContent
withdrawTxId={withdrawTxId}
onCancel={onCancel}
/>
)}
<DialogActions>
<WithdrawDialogContent isPending={pending} withdrawTxId={withdrawTxId}>
{withdrawTxId === null ? (
<AddressInputPage
setWithdrawAddress={setWithdrawAddress}
withdrawAddress={withdrawAddress}
setWithdrawAddressValid={setWithdrawAddressValid}
/>
) : (
<BtcTxInMempoolPageContent
withdrawTxId={withdrawTxId}
onCancel={onCancel}
/>
)}
</WithdrawDialogContent>
<DialogActions>
<Button onClick={onCancel} color="primary" disabled={pending}>
{withdrawTxId === null ? "Cancel" : "Done"}
</Button>
{withdrawTxId === null && (
<PromiseInvokeButton
displayErrorSnackbar
variant="contained"
color="primary"
disabled={!withdrawAddressValid}
@ -65,10 +69,6 @@ export default function WithdrawDialog({
>
Withdraw
</PromiseInvokeButton>
) : (
<Button onClick={onCancel} color="primary" disabled={pending}>
Close
</Button>
)}
</DialogActions>
</Dialog>

View File

@ -1,27 +1,31 @@
import { ReactNode } from 'react';
import { Box, DialogContent, makeStyles } from '@material-ui/core';
import WithdrawStepper from './WithdrawStepper';
import { ReactNode } from "react";
import { Box, DialogContent, makeStyles } from "@material-ui/core";
import WithdrawStepper from "./WithdrawStepper";
const useStyles = makeStyles({
outer: {
minHeight: '15rem',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
minHeight: "15rem",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
},
});
export default function WithdrawDialogContent({
children,
isPending,
withdrawTxId,
}: {
children: ReactNode;
isPending: boolean;
withdrawTxId: string | null;
}) {
const classes = useStyles();
return (
<DialogContent dividers className={classes.outer}>
<Box>{children}</Box>
<WithdrawStepper />
<WithdrawStepper isPending={isPending} withdrawTxId={withdrawTxId} />
</DialogContent>
);
}

View File

@ -1,27 +0,0 @@
import { useAppSelector, useIsRpcEndpointBusy } from 'store/hooks';
import { RpcMethod } from 'models/rpcModel';
import AddressInputPage from './pages/AddressInputPage';
import InitiatedPage from './pages/InitiatedPage';
import BtcTxInMempoolPageContent from './pages/BitcoinWithdrawTxInMempoolPage';
export default function WithdrawStatePage({
onCancel,
}: {
onCancel: () => void;
}) {
const isRpcEndpointBusy = useIsRpcEndpointBusy(RpcMethod.WITHDRAW_BTC);
const withdrawTxId = useAppSelector((state) => state.rpc.state.withdrawTxId);
if (withdrawTxId !== null) {
return (
<BtcTxInMempoolPageContent
withdrawTxId={withdrawTxId}
onCancel={onCancel}
/>
);
}
if (isRpcEndpointBusy) {
return <InitiatedPage onCancel={onCancel} />;
}
return <AddressInputPage onCancel={onCancel} />;
}

View File

@ -1,12 +1,8 @@
import { Step, StepLabel, Stepper } from '@material-ui/core';
import { useAppSelector, useIsRpcEndpointBusy } from 'store/hooks';
import { RpcMethod } from 'models/rpcModel';
import { Step, StepLabel, Stepper } from "@material-ui/core";
import { useAppSelector, useIsRpcEndpointBusy } from "store/hooks";
function getActiveStep(
isWithdrawInProgress: boolean,
withdrawTxId: string | null,
) {
if (isWithdrawInProgress) {
function getActiveStep(isPending: boolean, withdrawTxId: string | null) {
if (isPending) {
return 1;
}
if (withdrawTxId !== null) {
@ -15,12 +11,15 @@ function getActiveStep(
return 0;
}
export default function WithdrawStepper() {
const isWithdrawInProgress = useIsRpcEndpointBusy(RpcMethod.WITHDRAW_BTC);
const withdrawTxId = useAppSelector((s) => s.rpc.state.withdrawTxId);
export default function WithdrawStepper({
isPending,
withdrawTxId,
}: {
isPending: boolean;
withdrawTxId: string | null;
}) {
return (
<Stepper activeStep={getActiveStep(isWithdrawInProgress, withdrawTxId)}>
<Stepper activeStep={getActiveStep(isPending, withdrawTxId)}>
<Step key={0}>
<StepLabel>Enter withdraw address</StepLabel>
</Step>

View File

@ -15,20 +15,18 @@ export default function AddressInputPage({
}) {
return (
<>
<WithdrawDialogContent>
<DialogContentText>
To withdraw the BTC of the internal wallet, please enter an address.
All funds will be sent to that address.
</DialogContentText>
<DialogContentText>
To withdraw the Bitcoin inside the internal wallet, please enter an
address. All funds will be sent to that address.
</DialogContentText>
<BitcoinAddressTextField
address={withdrawAddress}
onAddressChange={setWithdrawAddress}
onAddressValidityChange={setWithdrawAddressValid}
helperText="All Bitcoin of the internal wallet will be transferred to this address"
fullWidth
/>
</WithdrawDialogContent>
<BitcoinAddressTextField
address={withdrawAddress}
onAddressChange={setWithdrawAddress}
onAddressValidityChange={setWithdrawAddressValid}
helperText="All Bitcoin of the internal wallet will be transferred to this address"
fullWidth
/>
</>
);
}

View File

@ -11,18 +11,16 @@ export default function BtcTxInMempoolPageContent({
}) {
return (
<>
<WithdrawDialogContent>
<DialogContentText>
All funds of the internal Bitcoin wallet have been transferred to your
withdraw address.
</DialogContentText>
<BitcoinTransactionInfoBox
txId={withdrawTxId}
loading={false}
title="Bitcoin Withdraw Transaction"
additionalContent={null}
/>
</WithdrawDialogContent>
<DialogContentText>
All funds of the internal Bitcoin wallet have been transferred to your
withdraw address.
</DialogContentText>
<BitcoinTransactionInfoBox
txId={withdrawTxId}
loading={false}
title="Bitcoin Withdraw Transaction"
additionalContent={null}
/>
</>
);
}

View File

@ -1,6 +1,6 @@
import { Drawer, makeStyles, Box } from '@material-ui/core';
import NavigationHeader from './NavigationHeader';
import NavigationFooter from './NavigationFooter';
import { Drawer, makeStyles, Box } from "@material-ui/core";
import NavigationHeader from "./NavigationHeader";
import NavigationFooter from "./NavigationFooter";
export const drawerWidth = 240;
@ -13,11 +13,11 @@ const useStyles = makeStyles({
width: drawerWidth,
},
drawerContainer: {
overflow: 'auto',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
height: '100%',
overflow: "auto",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
height: "100%",
},
});

View File

@ -1,24 +1,24 @@
import RedditIcon from '@material-ui/icons/Reddit';
import GitHubIcon from '@material-ui/icons/GitHub';
import { Box, makeStyles } from '@material-ui/core';
import LinkIconButton from '../icons/LinkIconButton';
import UnfinishedSwapsAlert from '../alert/UnfinishedSwapsAlert';
import FundsLeftInWalletAlert from '../alert/FundsLeftInWalletAlert';
import RpcStatusAlert from '../alert/RpcStatusAlert';
import DiscordIcon from '../icons/DiscordIcon';
import { DISCORD_URL } from '../pages/help/ContactInfoBox';
import MoneroWalletRpcUpdatingAlert from '../alert/MoneroWalletRpcUpdatingAlert';
import RedditIcon from "@material-ui/icons/Reddit";
import GitHubIcon from "@material-ui/icons/GitHub";
import { Box, makeStyles } from "@material-ui/core";
import LinkIconButton from "../icons/LinkIconButton";
import UnfinishedSwapsAlert from "../alert/UnfinishedSwapsAlert";
import FundsLeftInWalletAlert from "../alert/FundsLeftInWalletAlert";
import RpcStatusAlert from "../alert/RpcStatusAlert";
import DiscordIcon from "../icons/DiscordIcon";
import { DISCORD_URL } from "../pages/help/ContactInfoBox";
import MoneroWalletRpcUpdatingAlert from "../alert/MoneroWalletRpcUpdatingAlert";
const useStyles = makeStyles((theme) => ({
outer: {
display: 'flex',
flexDirection: 'column',
display: "flex",
flexDirection: "column",
padding: theme.spacing(1),
gap: theme.spacing(1),
},
linksOuter: {
display: 'flex',
justifyContent: 'space-evenly',
display: "flex",
justifyContent: "space-evenly",
},
}));

View File

@ -1,10 +1,10 @@
import { Box, List } from '@material-ui/core';
import SwapHorizOutlinedIcon from '@material-ui/icons/SwapHorizOutlined';
import HistoryOutlinedIcon from '@material-ui/icons/HistoryOutlined';
import AccountBalanceWalletIcon from '@material-ui/icons/AccountBalanceWallet';
import HelpOutlineIcon from '@material-ui/icons/HelpOutline';
import RouteListItemIconButton from './RouteListItemIconButton';
import UnfinishedSwapsBadge from './UnfinishedSwapsCountBadge';
import { Box, List } from "@material-ui/core";
import SwapHorizOutlinedIcon from "@material-ui/icons/SwapHorizOutlined";
import HistoryOutlinedIcon from "@material-ui/icons/HistoryOutlined";
import AccountBalanceWalletIcon from "@material-ui/icons/AccountBalanceWallet";
import HelpOutlineIcon from "@material-ui/icons/HelpOutline";
import RouteListItemIconButton from "./RouteListItemIconButton";
import UnfinishedSwapsBadge from "./UnfinishedSwapsCountBadge";
export default function NavigationHeader() {
return (

View File

@ -1,6 +1,6 @@
import { ReactNode } from 'react';
import { useNavigate } from 'react-router-dom';
import { ListItem, ListItemIcon, ListItemText } from '@material-ui/core';
import { ReactNode } from "react";
import { useNavigate } from "react-router-dom";
import { ListItem, ListItemIcon, ListItemText } from "@material-ui/core";
export default function RouteListItemIconButton({
name,

View File

@ -1,5 +1,5 @@
import { Badge } from '@material-ui/core';
import { useResumeableSwapsCount } from 'store/hooks';
import { Badge } from "@material-ui/core";
import { useResumeableSwapsCount } from "store/hooks";
export default function UnfinishedSwapsBadge({
children,

View File

@ -1,7 +1,7 @@
import { useState } from 'react';
import { Box, IconButton, TextField } from '@material-ui/core';
import SearchIcon from '@material-ui/icons/Search';
import CloseIcon from '@material-ui/icons/Close';
import { useState } from "react";
import { Box, IconButton, TextField } from "@material-ui/core";
import SearchIcon from "@material-ui/icons/Search";
import CloseIcon from "@material-ui/icons/Close";
export function ExpandableSearchBox({
query,
@ -13,8 +13,8 @@ export function ExpandableSearchBox({
const [expanded, setExpanded] = useState(false);
return (
<Box style={{ display: 'flex', justifyContent: 'center' }}>
<Box style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<Box style={{ display: "flex", justifyContent: "center" }}>
<Box style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
{expanded ? (
<>
<TextField
@ -26,7 +26,7 @@ export function ExpandableSearchBox({
<IconButton
onClick={() => {
setExpanded(false);
setQuery('');
setQuery("");
}}
size="small"
>

View File

@ -1,4 +1,4 @@
import humanizeDuration from 'humanize-duration';
import humanizeDuration from "humanize-duration";
const AVG_BLOCK_TIME_MS = 10 * 60 * 1000;
@ -10,7 +10,7 @@ export default function HumanizedBitcoinBlockDuration({
return (
<>
{`${humanizeDuration(blocks * AVG_BLOCK_TIME_MS, {
conjunction: ' and ',
conjunction: " and ",
})} (${blocks} blocks)`}
</>
);

View File

@ -1,8 +1,8 @@
import TreeView from '@material-ui/lab/TreeView';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import TreeItem from '@material-ui/lab/TreeItem';
import ScrollablePaperTextBox from './ScrollablePaperTextBox';
import TreeView from "@material-ui/lab/TreeView";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import TreeItem from "@material-ui/lab/TreeItem";
import ScrollablePaperTextBox from "./ScrollablePaperTextBox";
interface JsonTreeViewProps {
data: any;
@ -13,7 +13,7 @@ export default function JsonTreeView({ data, label }: JsonTreeViewProps) {
const renderTree = (nodes: any, parentId: string) => {
return Object.keys(nodes).map((key, _) => {
const nodeId = `${parentId}.${key}`;
if (typeof nodes[key] === 'object' && nodes[key] !== null) {
if (typeof nodes[key] === "object" && nodes[key] !== null) {
return (
<TreeItem nodeId={nodeId} label={key} key={nodeId}>
{renderTree(nodes[key], nodeId)}
@ -38,10 +38,10 @@ export default function JsonTreeView({ data, label }: JsonTreeViewProps) {
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
defaultExpanded={['root']}
defaultExpanded={["root"]}
>
<TreeItem nodeId="root" label={label}>
{renderTree(data ?? {}, 'root')}
{renderTree(data ?? {}, "root")}
</TreeItem>
</TreeView>,
]}

View File

@ -1,6 +1,6 @@
import React from 'react';
import Button, { ButtonProps } from '@material-ui/core/Button';
import CircularProgress from '@material-ui/core/CircularProgress';
import React from "react";
import Button, { ButtonProps } from "@material-ui/core/Button";
import CircularProgress from "@material-ui/core/CircularProgress";
interface LoadingButtonProps extends ButtonProps {
loading: boolean;

View File

@ -1,47 +1,47 @@
import { Box, Chip, Typography } from '@material-ui/core';
import { useMemo, useState } from 'react';
import { CliLog } from 'models/cliModel';
import { logsToRawString } from 'utils/parseUtils';
import ScrollablePaperTextBox from './ScrollablePaperTextBox';
import { Box, Chip, Typography } from "@material-ui/core";
import { useMemo, useState } from "react";
import { CliLog } from "models/cliModel";
import { logsToRawString } from "utils/parseUtils";
import ScrollablePaperTextBox from "./ScrollablePaperTextBox";
function RenderedCliLog({ log }: { log: CliLog }) {
const { timestamp, level, fields } = log;
const levelColorMap = {
DEBUG: '#1976d2', // Blue
INFO: '#388e3c', // Green
WARN: '#fbc02d', // Yellow
ERROR: '#d32f2f', // Red
TRACE: '#8e24aa', // Purple
DEBUG: "#1976d2", // Blue
INFO: "#388e3c", // Green
WARN: "#fbc02d", // Yellow
ERROR: "#d32f2f", // Red
TRACE: "#8e24aa", // Purple
};
return (
<Box>
<Box
style={{
display: 'flex',
gap: '0.3rem',
alignItems: 'center',
display: "flex",
gap: "0.3rem",
alignItems: "center",
}}
>
<Chip
label={level}
size="small"
style={{ backgroundColor: levelColorMap[level], color: 'white' }}
style={{ backgroundColor: levelColorMap[level], color: "white" }}
/>
<Chip label={timestamp} size="small" variant="outlined" />
<Typography variant="subtitle2">{fields.message}</Typography>
</Box>
<Box
sx={{
paddingLeft: '1rem',
paddingTop: '0.2rem',
display: 'flex',
flexDirection: 'column',
paddingLeft: "1rem",
paddingTop: "0.2rem",
display: "flex",
flexDirection: "column",
}}
>
{Object.entries(fields).map(([key, value]) => {
if (key !== 'message') {
if (key !== "message") {
return (
<Typography variant="caption" key={key}>
{key}: {JSON.stringify(value)}
@ -62,7 +62,7 @@ export default function CliLogsBox({
label: string;
logs: (CliLog | string)[];
}) {
const [searchQuery, setSearchQuery] = useState<string>('');
const [searchQuery, setSearchQuery] = useState<string>("");
const memoizedLogs = useMemo(() => {
if (searchQuery.length === 0) {
@ -80,7 +80,7 @@ export default function CliLogsBox({
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
rows={memoizedLogs.map((log) =>
typeof log === 'string' ? (
typeof log === "string" ? (
<Typography component="pre">{log}</Typography>
) : (
<RenderedCliLog log={log} key={JSON.stringify(log)} />

View File

@ -1,12 +1,12 @@
import { Box, Divider, IconButton, Paper, Typography } from '@material-ui/core';
import { ReactNode, useRef } from 'react';
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp';
import { VList, VListHandle } from 'virtua';
import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined';
import { ExpandableSearchBox } from './ExpandableSearchBox';
import { Box, Divider, IconButton, Paper, Typography } from "@material-ui/core";
import { ReactNode, useRef } from "react";
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
import { VList, VListHandle } from "virtua";
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
import { ExpandableSearchBox } from "./ExpandableSearchBox";
const MIN_HEIGHT = '10rem';
const MIN_HEIGHT = "10rem";
export default function ScrollablePaperTextBox({
rows,
@ -41,31 +41,31 @@ export default function ScrollablePaperTextBox({
<Paper
variant="outlined"
style={{
display: 'flex',
flexDirection: 'column',
gap: '0.5rem',
padding: '0.5rem',
width: '100%',
display: "flex",
flexDirection: "column",
gap: "0.5rem",
padding: "0.5rem",
width: "100%",
}}
>
<Typography>{title}</Typography>
<Divider />
<Box
style={{
overflow: 'auto',
whiteSpace: 'nowrap',
overflow: "auto",
whiteSpace: "nowrap",
maxHeight: minHeight,
minHeight,
display: 'flex',
flexDirection: 'column',
gap: '0.5rem',
display: "flex",
flexDirection: "column",
gap: "0.5rem",
}}
>
<VList ref={virtuaEl} style={{ height: MIN_HEIGHT, width: '100%' }}>
<VList ref={virtuaEl} style={{ height: MIN_HEIGHT, width: "100%" }}>
{rows}
</VList>
</Box>
<Box style={{ display: 'flex', gap: '0.5rem' }}>
<Box style={{ display: "flex", gap: "0.5rem" }}>
<IconButton onClick={onCopy} size="small">
<FileCopyOutlinedIcon />
</IconButton>

View File

@ -1,17 +1,17 @@
import { Box, Button, makeStyles, Typography } from '@material-ui/core';
import InfoBox from '../../modal/swap/InfoBox';
import { Box, Button, makeStyles, Typography } from "@material-ui/core";
import InfoBox from "../../modal/swap/InfoBox";
const useStyles = makeStyles((theme) => ({
spacedBox: {
display: 'flex',
display: "flex",
gap: theme.spacing(1),
},
}));
const GITHUB_ISSUE_URL =
'https://github.com/UnstoppableSwap/unstoppableswap-gui/issues/new/choose';
const MATRIX_ROOM_URL = 'https://matrix.to/#/#unstoppableswap:matrix.org';
export const DISCORD_URL = 'https://discord.gg/APJ6rJmq';
"https://github.com/UnstoppableSwap/unstoppableswap-gui/issues/new/choose";
const MATRIX_ROOM_URL = "https://matrix.to/#/#unstoppableswap:matrix.org";
export const DISCORD_URL = "https://discord.gg/APJ6rJmq";
export default function ContactInfoBox() {
const classes = useStyles();

View File

@ -1,9 +1,9 @@
import { Typography } from '@material-ui/core';
import DepositAddressInfoBox from '../../modal/swap/DepositAddressInfoBox';
import MoneroIcon from '../../icons/MoneroIcon';
import { Typography } from "@material-ui/core";
import DepositAddressInfoBox from "../../modal/swap/DepositAddressInfoBox";
import MoneroIcon from "../../icons/MoneroIcon";
const XMR_DONATE_ADDRESS =
'87jS4C7ngk9EHdqFFuxGFgg8AyH63dRUoULshWDybFJaP75UA89qsutG5B1L1QTc4w228nsqsv8EjhL7bz8fB3611Mh98mg';
"87jS4C7ngk9EHdqFFuxGFgg8AyH63dRUoULshWDybFJaP75UA89qsutG5B1L1QTc4w228nsqsv8EjhL7bz8fB3611Mh98mg";
export default function DonateInfoBox() {
return (

View File

@ -1,7 +1,7 @@
import { Button, Typography } from '@material-ui/core';
import { useState } from 'react';
import InfoBox from '../../modal/swap/InfoBox';
import FeedbackDialog from '../../modal/feedback/FeedbackDialog';
import { Button, Typography } from "@material-ui/core";
import { useState } from "react";
import InfoBox from "../../modal/swap/InfoBox";
import FeedbackDialog from "../../modal/feedback/FeedbackDialog";
export default function FeedbackInfoBox() {
const [showDialog, setShowDialog] = useState(false);

View File

@ -1,15 +1,15 @@
import { Box, makeStyles } from '@material-ui/core';
import ContactInfoBox from './ContactInfoBox';
import FeedbackInfoBox from './FeedbackInfoBox';
import DonateInfoBox from './DonateInfoBox';
import TorInfoBox from './TorInfoBox';
import RpcControlBox from './RpcControlBox';
import { Box, makeStyles } from "@material-ui/core";
import ContactInfoBox from "./ContactInfoBox";
import FeedbackInfoBox from "./FeedbackInfoBox";
import DonateInfoBox from "./DonateInfoBox";
import TorInfoBox from "./TorInfoBox";
import RpcControlBox from "./RpcControlBox";
const useStyles = makeStyles((theme) => ({
outer: {
display: 'flex',
display: "flex",
gap: theme.spacing(2),
flexDirection: 'column',
flexDirection: "column",
},
}));

View File

@ -1,18 +1,18 @@
import { Box, makeStyles } from '@material-ui/core';
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 { RpcProcessStateType } from 'models/rpcModel';
import InfoBox from '../../modal/swap/InfoBox';
import CliLogsBox from '../../other/RenderedCliLog';
import FolderOpenIcon from '@material-ui/icons/FolderOpen';
import { Box, makeStyles } from "@material-ui/core";
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 { RpcProcessStateType } from "models/rpcModel";
import InfoBox from "../../modal/swap/InfoBox";
import CliLogsBox from "../../other/RenderedCliLog";
import FolderOpenIcon from "@material-ui/icons/FolderOpen";
const useStyles = makeStyles((theme) => ({
actionsOuter: {
display: 'flex',
display: "flex",
gap: theme.spacing(1),
alignItems: 'center',
alignItems: "center",
},
}));

View File

@ -1,14 +1,14 @@
import { Box, makeStyles, Typography } from '@material-ui/core';
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/InfoBox';
import CliLogsBox from '../../other/RenderedCliLog';
import { Box, makeStyles, Typography } from "@material-ui/core";
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/InfoBox";
import CliLogsBox from "../../other/RenderedCliLog";
const useStyles = makeStyles((theme) => ({
actionsOuter: {
display: 'flex',
display: "flex",
gap: theme.spacing(1),
},
}));
@ -24,10 +24,10 @@ export default function TorInfoBox() {
mainContent={
<Box
style={{
width: '100%',
display: 'flex',
flexDirection: 'column',
gap: '8px',
width: "100%",
display: "flex",
flexDirection: "column",
gap: "8px",
}}
>
<Typography variant="subtitle2">
@ -37,7 +37,7 @@ export default function TorInfoBox() {
below. If Tor is running, all traffic will be routed through it and
the swap provider will not be able to see your IP address.
</Typography>
<CliLogsBox label="Tor Daemon Logs" logs={torStdOut.split('\n')} />
<CliLogsBox label="Tor Daemon Logs" logs={torStdOut.split("\n")} />
</Box>
}
additionalContent={

View File

@ -1,8 +1,8 @@
import { Typography } from '@material-ui/core';
import { useIsSwapRunning } from 'store/hooks';
import HistoryTable from './table/HistoryTable';
import SwapDialog from '../../modal/swap/SwapDialog';
import SwapTxLockAlertsBox from '../../alert/SwapTxLockAlertsBox';
import { Typography } from "@material-ui/core";
import { useIsSwapRunning } from "store/hooks";
import HistoryTable from "./table/HistoryTable";
import SwapDialog from "../../modal/swap/SwapDialog";
import SwapTxLockAlertsBox from "../../alert/SwapTxLockAlertsBox";
export default function HistoryPage() {
const showDialog = useIsSwapRunning();

View File

@ -1,98 +1,86 @@
import {
Box,
Collapse,
IconButton,
makeStyles,
TableCell,
TableRow,
Box,
Collapse,
IconButton,
makeStyles,
TableCell,
TableRow,
} from "@material-ui/core";
import { useState } from "react";
import ArrowForwardIcon from "@material-ui/icons/ArrowForward";
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
import {
getHumanReadableDbStateType,
getSwapBtcAmount,
getSwapXmrAmount,
GetSwapInfoResponse,
getHumanReadableDbStateType,
getSwapBtcAmount,
getSwapXmrAmount,
GetSwapInfoResponse,
} from "../../../../../models/rpcModel";
import HistoryRowActions from "./HistoryRowActions";
import HistoryRowExpanded from "./HistoryRowExpanded";
import { BitcoinAmount, MoneroAmount } from "../../../other/Units";
type HistoryRowProps = {
swap: GetSwapInfoResponse;
swap: GetSwapInfoResponse;
};
const useStyles = makeStyles((theme) => ({
amountTransferContainer: {
display: "flex",
alignItems: "center",
gap: theme.spacing(1),
},
amountTransferContainer: {
display: "flex",
alignItems: "center",
gap: theme.spacing(1),
},
}));
function AmountTransfer({
btcAmount,
xmrAmount,
btcAmount,
xmrAmount,
}: {
xmrAmount: number;
btcAmount: number;
xmrAmount: number;
btcAmount: number;
}) {
const classes = useStyles();
const classes = useStyles();
return (
<Box className={classes.amountTransferContainer}>
<BitcoinAmount amount={btcAmount} />
<ArrowForwardIcon />
<MoneroAmount amount={xmrAmount} />
</Box>
);
return (
<Box className={classes.amountTransferContainer}>
<BitcoinAmount amount={btcAmount} />
<ArrowForwardIcon />
<MoneroAmount amount={xmrAmount} />
</Box>
);
}
export default function HistoryRow({ swap }: HistoryRowProps) {
const btcAmount = getSwapBtcAmount(swap);
const xmrAmount = getSwapXmrAmount(swap);
const btcAmount = getSwapBtcAmount(swap);
const xmrAmount = getSwapXmrAmount(swap);
const [expanded, setExpanded] = useState(false);
const [expanded, setExpanded] = useState(false);
return (
<>
<TableRow>
<TableCell>
<IconButton
size="small"
onClick={() => setExpanded(!expanded)}
>
{expanded ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</TableCell>
<TableCell>{swap.swap_id.substring(0, 5)}...</TableCell>
<TableCell>
<AmountTransfer
xmrAmount={xmrAmount}
btcAmount={btcAmount}
/>
</TableCell>
<TableCell>
{getHumanReadableDbStateType(swap.state_name)}
</TableCell>
<TableCell>
<HistoryRowActions swap={swap} />
</TableCell>
</TableRow>
return (
<>
<TableRow>
<TableCell>
<IconButton size="small" onClick={() => setExpanded(!expanded)}>
{expanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
</TableCell>
<TableCell>{swap.swap_id.substring(0, 5)}...</TableCell>
<TableCell>
<AmountTransfer xmrAmount={xmrAmount} btcAmount={btcAmount} />
</TableCell>
<TableCell>{getHumanReadableDbStateType(swap.state_name)}</TableCell>
<TableCell>
<HistoryRowActions swap={swap} />
</TableCell>
</TableRow>
<TableRow>
<TableCell style={{ padding: 0 }} colSpan={6}>
<Collapse in={expanded} timeout="auto">
{expanded && <HistoryRowExpanded swap={swap} />}
</Collapse>
</TableCell>
</TableRow>
</>
);
<TableRow>
<TableCell style={{ padding: 0 }} colSpan={6}>
<Collapse in={expanded} timeout="auto">
{expanded && <HistoryRowExpanded swap={swap} />}
</Collapse>
</TableCell>
</TableRow>
</>
);
}

View File

@ -6,85 +6,85 @@ import { green, red } from "@material-ui/core/colors";
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
import IpcInvokeButton from "../../../IpcInvokeButton";
import {
GetSwapInfoResponse,
SwapStateName,
isSwapStateNamePossiblyCancellableSwap,
isSwapStateNamePossiblyRefundableSwap,
GetSwapInfoResponse,
SwapStateName,
isSwapStateNamePossiblyCancellableSwap,
isSwapStateNamePossiblyRefundableSwap,
} from "../../../../../models/rpcModel";
export function SwapResumeButton({
swap,
...props
swap,
...props
}: { swap: GetSwapInfoResponse } & ButtonProps) {
return (
<IpcInvokeButton
variant="contained"
color="primary"
disabled={swap.completed}
ipcChannel="spawn-resume-swap"
ipcArgs={[swap.swap_id]}
endIcon={<PlayArrowIcon />}
requiresRpc
{...props}
>
Resume
</IpcInvokeButton>
);
return (
<IpcInvokeButton
variant="contained"
color="primary"
disabled={swap.completed}
ipcChannel="spawn-resume-swap"
ipcArgs={[swap.swap_id]}
endIcon={<PlayArrowIcon />}
requiresRpc
{...props}
>
Resume
</IpcInvokeButton>
);
}
export function SwapCancelRefundButton({
swap,
...props
swap,
...props
}: { swap: GetSwapInfoResponse } & ButtonProps) {
const cancelOrRefundable =
isSwapStateNamePossiblyCancellableSwap(swap.state_name) ||
isSwapStateNamePossiblyRefundableSwap(swap.state_name);
const cancelOrRefundable =
isSwapStateNamePossiblyCancellableSwap(swap.state_name) ||
isSwapStateNamePossiblyRefundableSwap(swap.state_name);
if (!cancelOrRefundable) {
return <></>;
}
if (!cancelOrRefundable) {
return <></>;
}
return (
<IpcInvokeButton
ipcChannel="spawn-cancel-refund"
ipcArgs={[swap.swap_id]}
requiresRpc
displayErrorSnackbar={false}
{...props}
>
Attempt manual Cancel & Refund
</IpcInvokeButton>
);
return (
<IpcInvokeButton
ipcChannel="spawn-cancel-refund"
ipcArgs={[swap.swap_id]}
requiresRpc
displayErrorSnackbar={false}
{...props}
>
Attempt manual Cancel & Refund
</IpcInvokeButton>
);
}
export default function HistoryRowActions({
swap,
swap,
}: {
swap: GetSwapInfoResponse;
swap: GetSwapInfoResponse;
}) {
if (swap.state_name === SwapStateName.XmrRedeemed) {
return (
<Tooltip title="The swap is completed because you have redeemed the XMR">
<DoneIcon style={{ color: green[500] }} />
</Tooltip>
);
}
if (swap.state_name === SwapStateName.XmrRedeemed) {
return (
<Tooltip title="The swap is completed because you have redeemed the XMR">
<DoneIcon style={{ color: green[500] }} />
</Tooltip>
);
}
if (swap.state_name === SwapStateName.BtcRefunded) {
return (
<Tooltip title="The swap is completed because your BTC have been refunded">
<DoneIcon style={{ color: green[500] }} />
</Tooltip>
);
}
if (swap.state_name === SwapStateName.BtcRefunded) {
return (
<Tooltip title="The swap is completed because your BTC have been refunded">
<DoneIcon style={{ color: green[500] }} />
</Tooltip>
);
}
if (swap.state_name === SwapStateName.BtcPunished) {
return (
<Tooltip title="The swap is completed because you have been punished">
<ErrorIcon style={{ color: red[500] }} />
</Tooltip>
);
}
if (swap.state_name === SwapStateName.BtcPunished) {
return (
<Tooltip title="The swap is completed because you have been punished">
<ErrorIcon style={{ color: red[500] }} />
</Tooltip>
);
}
return <SwapResumeButton swap={swap} />;
return <SwapResumeButton swap={swap} />;
}

View File

@ -1,143 +1,134 @@
import {
Box,
Link,
makeStyles,
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
Box,
Link,
makeStyles,
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
} from "@material-ui/core";
import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
import { isTestnet } from "store/config";
import {
getHumanReadableDbStateType,
getSwapBtcAmount,
getSwapExchangeRate,
getSwapTxFees,
getSwapXmrAmount,
GetSwapInfoResponse,
getHumanReadableDbStateType,
getSwapBtcAmount,
getSwapExchangeRate,
getSwapTxFees,
getSwapXmrAmount,
GetSwapInfoResponse,
} from "../../../../../models/rpcModel";
import SwapLogFileOpenButton from "./SwapLogFileOpenButton";
import { SwapCancelRefundButton } from "./HistoryRowActions";
import { SwapMoneroRecoveryButton } from "./SwapMoneroRecoveryButton";
import {
BitcoinAmount,
MoneroAmount,
MoneroBitcoinExchangeRate,
BitcoinAmount,
MoneroAmount,
MoneroBitcoinExchangeRate,
} from "renderer/components/other/Units";
const useStyles = makeStyles((theme) => ({
outer: {
display: "grid",
padding: theme.spacing(1),
gap: theme.spacing(1),
},
actionsOuter: {
display: "flex",
flexDirection: "row",
gap: theme.spacing(1),
},
outer: {
display: "grid",
padding: theme.spacing(1),
gap: theme.spacing(1),
},
actionsOuter: {
display: "flex",
flexDirection: "row",
gap: theme.spacing(1),
},
}));
export default function HistoryRowExpanded({
swap,
swap,
}: {
swap: GetSwapInfoResponse;
swap: GetSwapInfoResponse;
}) {
const classes = useStyles();
const classes = useStyles();
const { seller, start_date: startDate } = swap;
const btcAmount = getSwapBtcAmount(swap);
const xmrAmount = getSwapXmrAmount(swap);
const txFees = getSwapTxFees(swap);
const exchangeRate = getSwapExchangeRate(swap);
const { seller, start_date: startDate } = swap;
const btcAmount = getSwapBtcAmount(swap);
const xmrAmount = getSwapXmrAmount(swap);
const txFees = getSwapTxFees(swap);
const exchangeRate = getSwapExchangeRate(swap);
return (
<Box className={classes.outer}>
<TableContainer>
<Table>
<TableBody>
<TableRow>
<TableCell>Started on</TableCell>
<TableCell>{startDate}</TableCell>
</TableRow>
<TableRow>
<TableCell>Swap ID</TableCell>
<TableCell>{swap.swap_id}</TableCell>
</TableRow>
<TableRow>
<TableCell>State Name</TableCell>
<TableCell>
{getHumanReadableDbStateType(swap.state_name)}
</TableCell>
</TableRow>
<TableRow>
<TableCell>Monero Amount</TableCell>
<TableCell>
<MoneroAmount amount={xmrAmount} />
</TableCell>
</TableRow>
<TableRow>
<TableCell>Bitcoin Amount</TableCell>
<TableCell>
<BitcoinAmount amount={btcAmount} />
</TableCell>
</TableRow>
<TableRow>
<TableCell>Exchange Rate</TableCell>
<TableCell>
<MoneroBitcoinExchangeRate
rate={exchangeRate}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell>Bitcoin Network Fees</TableCell>
<TableCell>
<BitcoinAmount amount={txFees} />
</TableCell>
</TableRow>
<TableRow>
<TableCell>Provider Address</TableCell>
<TableCell>
<Box>{seller.addresses.join(", ")}</Box>
</TableCell>
</TableRow>
<TableRow>
<TableCell>Bitcoin lock transaction</TableCell>
<TableCell>
<Link
href={getBitcoinTxExplorerUrl(
swap.tx_lock_id,
isTestnet(),
)}
target="_blank"
>
{swap.tx_lock_id}
</Link>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
<Box className={classes.actionsOuter}>
<SwapLogFileOpenButton
swapId={swap.swap_id}
variant="outlined"
size="small"
/>
<SwapCancelRefundButton
swap={swap}
variant="contained"
size="small"
/>
<SwapMoneroRecoveryButton
swap={swap}
variant="contained"
size="small"
/>
</Box>
</Box>
);
return (
<Box className={classes.outer}>
<TableContainer>
<Table>
<TableBody>
<TableRow>
<TableCell>Started on</TableCell>
<TableCell>{startDate}</TableCell>
</TableRow>
<TableRow>
<TableCell>Swap ID</TableCell>
<TableCell>{swap.swap_id}</TableCell>
</TableRow>
<TableRow>
<TableCell>State Name</TableCell>
<TableCell>
{getHumanReadableDbStateType(swap.state_name)}
</TableCell>
</TableRow>
<TableRow>
<TableCell>Monero Amount</TableCell>
<TableCell>
<MoneroAmount amount={xmrAmount} />
</TableCell>
</TableRow>
<TableRow>
<TableCell>Bitcoin Amount</TableCell>
<TableCell>
<BitcoinAmount amount={btcAmount} />
</TableCell>
</TableRow>
<TableRow>
<TableCell>Exchange Rate</TableCell>
<TableCell>
<MoneroBitcoinExchangeRate rate={exchangeRate} />
</TableCell>
</TableRow>
<TableRow>
<TableCell>Bitcoin Network Fees</TableCell>
<TableCell>
<BitcoinAmount amount={txFees} />
</TableCell>
</TableRow>
<TableRow>
<TableCell>Provider Address</TableCell>
<TableCell>
<Box>{seller.addresses.join(", ")}</Box>
</TableCell>
</TableRow>
<TableRow>
<TableCell>Bitcoin lock transaction</TableCell>
<TableCell>
<Link
href={getBitcoinTxExplorerUrl(swap.tx_lock_id, isTestnet())}
target="_blank"
>
{swap.tx_lock_id}
</Link>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
<Box className={classes.actionsOuter}>
<SwapLogFileOpenButton
swapId={swap.swap_id}
variant="outlined"
size="small"
/>
<SwapCancelRefundButton swap={swap} variant="contained" size="small" />
<SwapMoneroRecoveryButton
swap={swap}
variant="contained"
size="small"
/>
</Box>
</Box>
);
}

View File

@ -1,53 +1,53 @@
import {
Box,
makeStyles,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Box,
makeStyles,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from "@material-ui/core";
import { sortBy } from "lodash";
import { parseDateString } from "utils/parseUtils";
import {
useAppSelector,
useSwapInfosSortedByDate,
useAppSelector,
useSwapInfosSortedByDate,
} from "../../../../../store/hooks";
import HistoryRow from "./HistoryRow";
const useStyles = makeStyles((theme) => ({
outer: {
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(1),
},
outer: {
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(1),
},
}));
export default function HistoryTable() {
const classes = useStyles();
const swapSortedByDate = useSwapInfosSortedByDate();
const classes = useStyles();
const swapSortedByDate = useSwapInfosSortedByDate();
return (
<Box className={classes.outer}>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell />
<TableCell>ID</TableCell>
<TableCell>Amount</TableCell>
<TableCell>State</TableCell>
<TableCell />
</TableRow>
</TableHead>
<TableBody>
{swapSortedByDate.map((swap) => (
<HistoryRow swap={swap} key={swap.swap_id} />
))}
</TableBody>
</Table>
</TableContainer>
</Box>
);
return (
<Box className={classes.outer}>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell />
<TableCell>ID</TableCell>
<TableCell>Amount</TableCell>
<TableCell>State</TableCell>
<TableCell />
</TableRow>
</TableHead>
<TableBody>
{swapSortedByDate.map((swap) => (
<HistoryRow swap={swap} key={swap.swap_id} />
))}
</TableBody>
</Table>
</TableContainer>
</Box>
);
}

View File

@ -1,15 +1,15 @@
import { ButtonProps } from '@material-ui/core/Button/Button';
import { ButtonProps } from "@material-ui/core/Button/Button";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
} from '@material-ui/core';
import { useState } from 'react';
import { CliLog } from 'models/cliModel';
import IpcInvokeButton from '../../../IpcInvokeButton';
import CliLogsBox from '../../../other/RenderedCliLog';
} from "@material-ui/core";
import { useState } from "react";
import { CliLog } from "models/cliModel";
import IpcInvokeButton from "../../../IpcInvokeButton";
import CliLogsBox from "../../../other/RenderedCliLog";
export default function SwapLogFileOpenButton({
swapId,

View File

@ -1,120 +1,119 @@
import { ButtonProps } from "@material-ui/core/Button/Button";
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
Link,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
Link,
} from "@material-ui/core";
import { useAppDispatch, useAppSelector } from "store/hooks";
import { rpcResetMoneroRecoveryKeys } from "store/features/rpcSlice";
import {
GetSwapInfoResponse,
isSwapMoneroRecoverable,
GetSwapInfoResponse,
isSwapMoneroRecoverable,
} from "../../../../../models/rpcModel";
import IpcInvokeButton from "../../../IpcInvokeButton";
import DialogHeader from "../../../modal/DialogHeader";
import ScrollablePaperTextBox from "../../../other/ScrollablePaperTextBox";
function MoneroRecoveryKeysDialog({ swap }: { swap: GetSwapInfoResponse }) {
const dispatch = useAppDispatch();
const keys = useAppSelector((s) => s.rpc.state.moneroRecovery);
const dispatch = useAppDispatch();
const keys = useAppSelector((s) => s.rpc.state.moneroRecovery);
function onClose() {
dispatch(rpcResetMoneroRecoveryKeys());
}
function onClose() {
dispatch(rpcResetMoneroRecoveryKeys());
}
if (keys === null || keys.swapId !== swap.swap_id) {
return <></>;
}
if (keys === null || keys.swapId !== swap.swap_id) {
return <></>;
}
return (
<Dialog open onClose={onClose} maxWidth="sm" fullWidth>
<DialogHeader
title={`Recovery Keys for swap ${swap.swap_id.substring(0, 5)}...`}
return (
<Dialog open onClose={onClose} maxWidth="sm" fullWidth>
<DialogHeader
title={`Recovery Keys for swap ${swap.swap_id.substring(0, 5)}...`}
/>
<DialogContent>
<DialogContentText>
You can use the keys below to manually redeem the Monero funds from
the multi-signature wallet.
<ul>
<li>
This is useful if the swap daemon fails to redeem the funds itself
</li>
<li>
If you have come this far, there is no risk of losing funds. You
are the only one with access to these keys and can use them to
access your funds
</li>
<li>
View{" "}
<Link
href="https://www.getmonero.org/resources/user-guides/restore_from_keys.html"
target="_blank"
rel="noreferrer"
>
this guide
</Link>{" "}
for a detailed description on how to import the keys and spend the
funds.
</li>
</ul>
</DialogContentText>
<Box
style={{
display: "flex",
gap: "0.5rem",
flexDirection: "column",
}}
>
{[
["Primary Address", keys.keys.address],
["View Key", keys.keys.view_key],
["Spend Key", keys.keys.spend_key],
["Restore Height", keys.keys.restore_height.toString()],
].map(([title, value]) => (
<ScrollablePaperTextBox
minHeight="2rem"
title={title}
copyValue={value}
rows={[value]}
/>
<DialogContent>
<DialogContentText>
You can use the keys below to manually redeem the Monero
funds from the multi-signature wallet.
<ul>
<li>
This is useful if the swap daemon fails to redeem
the funds itself
</li>
<li>
If you have come this far, there is no risk of
losing funds. You are the only one with access to
these keys and can use them to access your funds
</li>
<li>
View{" "}
<Link
href="https://www.getmonero.org/resources/user-guides/restore_from_keys.html"
target="_blank"
rel="noreferrer"
>
this guide
</Link>{" "}
for a detailed description on how to import the keys
and spend the funds.
</li>
</ul>
</DialogContentText>
<Box
style={{
display: "flex",
gap: "0.5rem",
flexDirection: "column",
}}
>
{[
["Primary Address", keys.keys.address],
["View Key", keys.keys.view_key],
["Spend Key", keys.keys.spend_key],
["Restore Height", keys.keys.restore_height.toString()],
].map(([title, value]) => (
<ScrollablePaperTextBox
minHeight="2rem"
title={title}
copyValue={value}
rows={[value]}
/>
))}
</Box>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary" variant="contained">
Done
</Button>
</DialogActions>
</Dialog>
);
))}
</Box>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary" variant="contained">
Done
</Button>
</DialogActions>
</Dialog>
);
}
export function SwapMoneroRecoveryButton({
swap,
...props
swap,
...props
}: { swap: GetSwapInfoResponse } & ButtonProps) {
const isRecoverable = isSwapMoneroRecoverable(swap.state_name);
const isRecoverable = isSwapMoneroRecoverable(swap.state_name);
if (!isRecoverable) {
return <></>;
}
if (!isRecoverable) {
return <></>;
}
return (
<>
<IpcInvokeButton
ipcChannel="spawn-monero-recovery"
ipcArgs={[swap.swap_id]}
requiresRpc
{...props}
>
Display Monero Recovery Keys
</IpcInvokeButton>
<MoneroRecoveryKeysDialog swap={swap} />
</>
);
return (
<>
<IpcInvokeButton
ipcChannel="spawn-monero-recovery"
ipcArgs={[swap.swap_id]}
requiresRpc
{...props}
>
Display Monero Recovery Keys
</IpcInvokeButton>
<MoneroRecoveryKeysDialog swap={swap} />
</>
);
}

View File

@ -1,7 +1,7 @@
import { Box } from '@material-ui/core';
import { Alert, AlertTitle } from '@material-ui/lab';
import { removeAlert } from 'store/features/alertsSlice';
import { useAppDispatch, useAppSelector } from 'store/hooks';
import { Box } from "@material-ui/core";
import { Alert, AlertTitle } from "@material-ui/lab";
import { removeAlert } from "store/features/alertsSlice";
import { useAppDispatch, useAppSelector } from "store/hooks";
export default function ApiAlertsBox() {
const alerts = useAppSelector((state) => state.alerts.alerts);
@ -14,7 +14,7 @@ export default function ApiAlertsBox() {
if (alerts.length === 0) return null;
return (
<Box style={{ display: 'flex', justifyContent: 'center', gap: '1rem' }}>
<Box style={{ display: "flex", justifyContent: "center", gap: "1rem" }}>
{alerts.map((alert) => (
<Alert
variant="filled"

View File

@ -1,13 +1,13 @@
import { Box, makeStyles } from '@material-ui/core';
import SwapWidget from './SwapWidget';
import ApiAlertsBox from './ApiAlertsBox';
import { Box, makeStyles } from "@material-ui/core";
import SwapWidget from "./SwapWidget";
import ApiAlertsBox from "./ApiAlertsBox";
const useStyles = makeStyles((theme) => ({
outer: {
display: 'flex',
width: '100%',
flexDirection: 'column',
alignItems: 'center',
display: "flex",
width: "100%",
flexDirection: "column",
alignItems: "center",
paddingBottom: theme.spacing(1),
gap: theme.spacing(1),
},

View File

@ -1,4 +1,4 @@
import { ChangeEvent, useEffect, useState } from 'react';
import { ChangeEvent, useEffect, useState } from "react";
import {
makeStyles,
Box,
@ -7,21 +7,21 @@ import {
TextField,
LinearProgress,
Fab,
} from '@material-ui/core';
import InputAdornment from '@material-ui/core/InputAdornment';
import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward';
import SwapHorizIcon from '@material-ui/icons/SwapHoriz';
import { Alert } from '@material-ui/lab';
import { satsToBtc } from 'utils/conversionUtils';
import { useAppSelector } from 'store/hooks';
import { ExtendedProviderStatus } from 'models/apiModel';
import { isSwapState } from 'models/storeModel';
import SwapDialog from '../../modal/swap/SwapDialog';
import ProviderSelect from '../../modal/provider/ProviderSelect';
} from "@material-ui/core";
import InputAdornment from "@material-ui/core/InputAdornment";
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
import SwapHorizIcon from "@material-ui/icons/SwapHoriz";
import { Alert } from "@material-ui/lab";
import { satsToBtc } from "utils/conversionUtils";
import { useAppSelector } from "store/hooks";
import { ExtendedProviderStatus } from "models/apiModel";
import { isSwapState } from "models/storeModel";
import SwapDialog from "../../modal/swap/SwapDialog";
import ProviderSelect from "../../modal/provider/ProviderSelect";
import {
ListSellersDialogOpenButton,
ProviderSubmitDialogOpenButton,
} from '../../modal/provider/ProviderListDialog';
} from "../../modal/provider/ProviderListDialog";
// After RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN failed reconnection attempts we can assume the public registry is down
const RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN = 1;
@ -32,9 +32,9 @@ function isRegistryDown(reconnectionAttempts: number): boolean {
const useStyles = makeStyles((theme) => ({
inner: {
width: 'min(480px, 100%)',
minHeight: '150px',
display: 'grid',
width: "min(480px, 100%)",
minHeight: "150px",
display: "grid",
padding: theme.spacing(1),
gridGap: theme.spacing(1),
},
@ -48,19 +48,19 @@ const useStyles = makeStyles((theme) => ({
padding: theme.spacing(1),
},
swapIconOuter: {
display: 'flex',
justifyContent: 'center',
display: "flex",
justifyContent: "center",
},
swapIcon: {
marginRight: theme.spacing(1),
},
noProvidersAlertOuter: {
display: 'flex',
flexDirection: 'column',
display: "flex",
flexDirection: "column",
gap: theme.spacing(1),
},
noProvidersAlertButtonsOuter: {
display: 'flex',
display: "flex",
gap: theme.spacing(1),
},
}));
@ -111,7 +111,7 @@ function HasProviderSwapWidget({
function getBtcFieldError(): string | null {
const parsedBtcAmount = Number(btcFieldValue);
if (Number.isNaN(parsedBtcAmount)) {
return 'This is not a valid number';
return "This is not a valid number";
}
if (parsedBtcAmount < satsToBtc(selectedProvider.minSwapAmount)) {
return `The minimum swap amount is ${satsToBtc(

View File

@ -1,11 +1,11 @@
import { Box, makeStyles, Typography } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import WithdrawWidget from './WithdrawWidget';
import { Box, makeStyles, Typography } from "@material-ui/core";
import { Alert } from "@material-ui/lab";
import WithdrawWidget from "./WithdrawWidget";
const useStyles = makeStyles((theme) => ({
outer: {
display: 'flex',
flexDirection: 'column',
display: "flex",
flexDirection: "column",
gridGap: theme.spacing(0.5),
},
}));

View File

@ -1,18 +1,18 @@
import { Box, Button, makeStyles, Typography } from '@material-ui/core';
import { useState } from 'react';
import SendIcon from '@material-ui/icons/Send';
import { useAppSelector, useIsRpcEndpointBusy } from 'store/hooks';
import { RpcMethod } from 'models/rpcModel';
import BitcoinIcon from '../../icons/BitcoinIcon';
import WithdrawDialog from '../../modal/wallet/WithdrawDialog';
import WalletRefreshButton from './WalletRefreshButton';
import InfoBox from '../../modal/swap/InfoBox';
import { SatsAmount } from 'renderer/components/other/Units';
import { Box, Button, makeStyles, Typography } from "@material-ui/core";
import { useState } from "react";
import SendIcon from "@material-ui/icons/Send";
import { useAppSelector, useIsRpcEndpointBusy } from "store/hooks";
import { RpcMethod } from "models/rpcModel";
import BitcoinIcon from "../../icons/BitcoinIcon";
import WithdrawDialog from "../../modal/wallet/WithdrawDialog";
import WalletRefreshButton from "./WalletRefreshButton";
import InfoBox from "../../modal/swap/InfoBox";
import { SatsAmount } from "renderer/components/other/Units";
const useStyles = makeStyles((theme) => ({
title: {
alignItems: 'center',
display: 'flex',
alignItems: "center",
display: "flex",
gap: theme.spacing(0.5),
},
}));

View File

@ -3,14 +3,14 @@ import {
SnackbarKey,
SnackbarProvider,
useSnackbar,
} from 'notistack';
import { IconButton, styled } from '@material-ui/core';
import { Close } from '@material-ui/icons';
import { ReactNode } from 'react';
} from "notistack";
import { IconButton, styled } from "@material-ui/core";
import { Close } from "@material-ui/icons";
import { ReactNode } from "react";
const StyledMaterialDesignContent = styled(MaterialDesignContent)(() => ({
'&.notistack-MuiContent': {
maxWidth: '50vw',
"&.notistack-MuiContent": {
maxWidth: "50vw",
},
}));

Some files were not shown because too many files have changed in this diff Show More