mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-04-20 16:06:00 -04:00
wip: WithdrawDialog migrated to Tauri IPC
This commit is contained in:
parent
47821cbe79
commit
3d16ff6d5c
@ -24,5 +24,5 @@ export interface Alert {
|
||||
id: number;
|
||||
title: string;
|
||||
body: string;
|
||||
severity: 'info' | 'warning' | 'error';
|
||||
severity: "info" | "warning" | "error";
|
||||
}
|
||||
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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: {
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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: {
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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 (
|
||||
|
@ -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 (
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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 (
|
||||
|
@ -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 (
|
||||
|
@ -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(() => {
|
||||
|
@ -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);
|
||||
|
@ -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",
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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`}
|
||||
/>
|
||||
) : (
|
||||
|
@ -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: {
|
||||
|
@ -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",
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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: {
|
||||
|
@ -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 (
|
||||
|
@ -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>
|
||||
|
@ -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),
|
||||
},
|
||||
}));
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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),
|
||||
},
|
||||
}));
|
||||
|
@ -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} />;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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 (
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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 && (
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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} />;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||
|
||||
export default function BitcoinCancelledPage() {
|
||||
return <CircularProgressWithSubtitle description="Refunding your Bitcoin" />;
|
||||
|
@ -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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||
|
||||
export default function BitcoinRedeemedPage() {
|
||||
return <CircularProgressWithSubtitle description="Redeeming your Monero" />;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||
|
||||
export default function ReceivedQuotePage() {
|
||||
return (
|
||||
|
@ -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} />;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||
|
||||
export function SyncingMoneroWalletPage() {
|
||||
return (
|
||||
|
@ -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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||
|
||||
export default function XmrLockedPage() {
|
||||
return (
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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...";
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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 />}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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} />;
|
||||
}
|
@ -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>
|
||||
|
@ -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
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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%",
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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",
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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)`}
|
||||
</>
|
||||
);
|
||||
|
@ -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>,
|
||||
]}
|
||||
|
@ -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;
|
||||
|
@ -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)} />
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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 (
|
||||
|
@ -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);
|
||||
|
@ -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",
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -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",
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -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={
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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} />;
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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),
|
||||
},
|
||||
|
@ -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(
|
||||
|
@ -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),
|
||||
},
|
||||
}));
|
||||
|
@ -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),
|
||||
},
|
||||
}));
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user