mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-06-07 14:42:53 -04:00
wip: WithdrawDialog migrated to Tauri IPC
This commit is contained in:
parent
47821cbe79
commit
3d16ff6d5c
118 changed files with 1779 additions and 1868 deletions
|
@ -24,5 +24,5 @@ export interface Alert {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: string;
|
||||||
severity: 'info' | 'warning' | 'error';
|
severity: "info" | "warning" | "error";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
export enum SwapSpawnType {
|
export enum SwapSpawnType {
|
||||||
INIT = 'init',
|
INIT = "init",
|
||||||
RESUME = 'resume',
|
RESUME = "resume",
|
||||||
CANCEL_REFUND = 'cancel-refund',
|
CANCEL_REFUND = "cancel-refund",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CliLogSpanType = string | 'BitcoinWalletSubscription';
|
export type CliLogSpanType = string | "BitcoinWalletSubscription";
|
||||||
|
|
||||||
export interface CliLog {
|
export interface CliLog {
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'TRACE';
|
level: "DEBUG" | "INFO" | "WARN" | "ERROR" | "TRACE";
|
||||||
fields: {
|
fields: {
|
||||||
message: string;
|
message: string;
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
|
@ -20,12 +20,12 @@ export interface CliLog {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLog(log: unknown): log is CliLog {
|
export function isCliLog(log: unknown): log is CliLog {
|
||||||
if (log && typeof log === 'object') {
|
if (log && typeof log === "object") {
|
||||||
return (
|
return (
|
||||||
'timestamp' in (log as CliLog) &&
|
"timestamp" in (log as CliLog) &&
|
||||||
'level' in (log as CliLog) &&
|
"level" in (log as CliLog) &&
|
||||||
'fields' in (log as CliLog) &&
|
"fields" in (log as CliLog) &&
|
||||||
typeof (log as CliLog).fields?.message === 'string'
|
typeof (log as CliLog).fields?.message === "string"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -33,7 +33,7 @@ export function isCliLog(log: unknown): log is CliLog {
|
||||||
|
|
||||||
export interface CliLogStartedRpcServer extends CliLog {
|
export interface CliLogStartedRpcServer extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Started RPC server';
|
message: "Started RPC server";
|
||||||
addr: string;
|
addr: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -41,12 +41,12 @@ export interface CliLogStartedRpcServer extends CliLog {
|
||||||
export function isCliLogStartedRpcServer(
|
export function isCliLogStartedRpcServer(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogStartedRpcServer {
|
): log is CliLogStartedRpcServer {
|
||||||
return log.fields.message === 'Started RPC server';
|
return log.fields.message === "Started RPC server";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogReleasingSwapLockLog extends CliLog {
|
export interface CliLogReleasingSwapLockLog extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Releasing swap lock';
|
message: "Releasing swap lock";
|
||||||
swap_id: string;
|
swap_id: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -54,23 +54,23 @@ export interface CliLogReleasingSwapLockLog extends CliLog {
|
||||||
export function isCliLogReleasingSwapLockLog(
|
export function isCliLogReleasingSwapLockLog(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogReleasingSwapLockLog {
|
): log is CliLogReleasingSwapLockLog {
|
||||||
return log.fields.message === 'Releasing swap lock';
|
return log.fields.message === "Releasing swap lock";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogApiCallError extends CliLog {
|
export interface CliLogApiCallError extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'API call resulted in an error';
|
message: "API call resulted in an error";
|
||||||
err: string;
|
err: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLogApiCallError(log: CliLog): log is CliLogApiCallError {
|
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 {
|
export interface CliLogAcquiringSwapLockLog extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Acquiring swap lock';
|
message: "Acquiring swap lock";
|
||||||
swap_id: string;
|
swap_id: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -78,12 +78,12 @@ export interface CliLogAcquiringSwapLockLog extends CliLog {
|
||||||
export function isCliLogAcquiringSwapLockLog(
|
export function isCliLogAcquiringSwapLockLog(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogAcquiringSwapLockLog {
|
): log is CliLogAcquiringSwapLockLog {
|
||||||
return log.fields.message === 'Acquiring swap lock';
|
return log.fields.message === "Acquiring swap lock";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogReceivedQuote extends CliLog {
|
export interface CliLogReceivedQuote extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Received quote';
|
message: "Received quote";
|
||||||
price: string;
|
price: string;
|
||||||
minimum_amount: string;
|
minimum_amount: string;
|
||||||
maximum_amount: string;
|
maximum_amount: string;
|
||||||
|
@ -91,12 +91,12 @@ export interface CliLogReceivedQuote extends CliLog {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLogReceivedQuote(log: CliLog): log is CliLogReceivedQuote {
|
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 {
|
export interface CliLogWaitingForBtcDeposit extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Waiting for Bitcoin deposit';
|
message: "Waiting for Bitcoin deposit";
|
||||||
deposit_address: string;
|
deposit_address: string;
|
||||||
min_deposit_until_swap_will_start: string;
|
min_deposit_until_swap_will_start: string;
|
||||||
max_deposit_until_maximum_amount_is_reached: string;
|
max_deposit_until_maximum_amount_is_reached: string;
|
||||||
|
@ -111,24 +111,24 @@ export interface CliLogWaitingForBtcDeposit extends CliLog {
|
||||||
export function isCliLogWaitingForBtcDeposit(
|
export function isCliLogWaitingForBtcDeposit(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogWaitingForBtcDeposit {
|
): log is CliLogWaitingForBtcDeposit {
|
||||||
return log.fields.message === 'Waiting for Bitcoin deposit';
|
return log.fields.message === "Waiting for Bitcoin deposit";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogReceivedBtc extends CliLog {
|
export interface CliLogReceivedBtc extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Received Bitcoin';
|
message: "Received Bitcoin";
|
||||||
max_giveable: string;
|
max_giveable: string;
|
||||||
new_balance: string;
|
new_balance: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLogReceivedBtc(log: CliLog): log is CliLogReceivedBtc {
|
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 {
|
export interface CliLogDeterminedSwapAmount extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Determined swap amount';
|
message: "Determined swap amount";
|
||||||
amount: string;
|
amount: string;
|
||||||
fees: string;
|
fees: string;
|
||||||
};
|
};
|
||||||
|
@ -137,49 +137,49 @@ export interface CliLogDeterminedSwapAmount extends CliLog {
|
||||||
export function isCliLogDeterminedSwapAmount(
|
export function isCliLogDeterminedSwapAmount(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogDeterminedSwapAmount {
|
): log is CliLogDeterminedSwapAmount {
|
||||||
return log.fields.message === 'Determined swap amount';
|
return log.fields.message === "Determined swap amount";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogStartedSwap extends CliLog {
|
export interface CliLogStartedSwap extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Starting new swap';
|
message: "Starting new swap";
|
||||||
swap_id: string;
|
swap_id: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLogStartedSwap(log: CliLog): log is CliLogStartedSwap {
|
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 {
|
export interface CliLogPublishedBtcTx extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Published Bitcoin transaction';
|
message: "Published Bitcoin transaction";
|
||||||
txid: string;
|
txid: string;
|
||||||
kind: 'lock' | 'cancel' | 'withdraw' | 'refund';
|
kind: "lock" | "cancel" | "withdraw" | "refund";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLogPublishedBtcTx(
|
export function isCliLogPublishedBtcTx(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogPublishedBtcTx {
|
): log is CliLogPublishedBtcTx {
|
||||||
return log.fields.message === 'Published Bitcoin transaction';
|
return log.fields.message === "Published Bitcoin transaction";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogBtcTxFound extends CliLog {
|
export interface CliLogBtcTxFound extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Found relevant Bitcoin transaction';
|
message: "Found relevant Bitcoin transaction";
|
||||||
txid: string;
|
txid: string;
|
||||||
status: string;
|
status: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLogBtcTxFound(log: CliLog): log is CliLogBtcTxFound {
|
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 {
|
export interface CliLogBtcTxStatusChanged extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Bitcoin transaction status changed';
|
message: "Bitcoin transaction status changed";
|
||||||
txid: string;
|
txid: string;
|
||||||
new_status: string;
|
new_status: string;
|
||||||
};
|
};
|
||||||
|
@ -188,12 +188,12 @@ export interface CliLogBtcTxStatusChanged extends CliLog {
|
||||||
export function isCliLogBtcTxStatusChanged(
|
export function isCliLogBtcTxStatusChanged(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogBtcTxStatusChanged {
|
): log is CliLogBtcTxStatusChanged {
|
||||||
return log.fields.message === 'Bitcoin transaction status changed';
|
return log.fields.message === "Bitcoin transaction status changed";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogAliceLockedXmr extends CliLog {
|
export interface CliLogAliceLockedXmr extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Alice locked Monero';
|
message: "Alice locked Monero";
|
||||||
txid: string;
|
txid: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -201,12 +201,12 @@ export interface CliLogAliceLockedXmr extends CliLog {
|
||||||
export function isCliLogAliceLockedXmr(
|
export function isCliLogAliceLockedXmr(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogAliceLockedXmr {
|
): log is CliLogAliceLockedXmr {
|
||||||
return log.fields.message === 'Alice locked Monero';
|
return log.fields.message === "Alice locked Monero";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogReceivedXmrLockTxConfirmation extends CliLog {
|
export interface CliLogReceivedXmrLockTxConfirmation extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Received new confirmation for Monero lock tx';
|
message: "Received new confirmation for Monero lock tx";
|
||||||
txid: string;
|
txid: string;
|
||||||
seen_confirmations: string;
|
seen_confirmations: string;
|
||||||
needed_confirmations: string;
|
needed_confirmations: string;
|
||||||
|
@ -216,50 +216,50 @@ export interface CliLogReceivedXmrLockTxConfirmation extends CliLog {
|
||||||
export function isCliLogReceivedXmrLockTxConfirmation(
|
export function isCliLogReceivedXmrLockTxConfirmation(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogReceivedXmrLockTxConfirmation {
|
): 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 {
|
export interface CliLogAdvancingState extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Advancing state';
|
message: "Advancing state";
|
||||||
state:
|
state:
|
||||||
| 'quote has been requested'
|
| "quote has been requested"
|
||||||
| 'execution setup done'
|
| "execution setup done"
|
||||||
| 'btc is locked'
|
| "btc is locked"
|
||||||
| 'XMR lock transaction transfer proof received'
|
| "XMR lock transaction transfer proof received"
|
||||||
| 'xmr is locked'
|
| "xmr is locked"
|
||||||
| 'encrypted signature is sent'
|
| "encrypted signature is sent"
|
||||||
| 'btc is redeemed'
|
| "btc is redeemed"
|
||||||
| 'cancel timelock is expired'
|
| "cancel timelock is expired"
|
||||||
| 'btc is cancelled'
|
| "btc is cancelled"
|
||||||
| 'btc is refunded'
|
| "btc is refunded"
|
||||||
| 'xmr is redeemed'
|
| "xmr is redeemed"
|
||||||
| 'btc is punished'
|
| "btc is punished"
|
||||||
| 'safely aborted';
|
| "safely aborted";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLogAdvancingState(
|
export function isCliLogAdvancingState(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogAdvancingState {
|
): log is CliLogAdvancingState {
|
||||||
return log.fields.message === 'Advancing state';
|
return log.fields.message === "Advancing state";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogRedeemedXmr extends CliLog {
|
export interface CliLogRedeemedXmr extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Successfully transferred XMR to wallet';
|
message: "Successfully transferred XMR to wallet";
|
||||||
monero_receive_address: string;
|
monero_receive_address: string;
|
||||||
txid: string;
|
txid: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLogRedeemedXmr(log: CliLog): log is CliLogRedeemedXmr {
|
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 {
|
export interface YouHaveBeenPunishedCliLog extends CliLog {
|
||||||
fields: {
|
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: CliLog,
|
||||||
): log is YouHaveBeenPunishedCliLog {
|
): log is YouHaveBeenPunishedCliLog {
|
||||||
return (
|
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 {
|
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 {
|
export function getCliLogSpanLogReferenceId(log: CliLog): string | null {
|
||||||
return (
|
return (
|
||||||
getCliLogSpanAttribute<string>(log, 'log_reference_id')?.replace(
|
getCliLogSpanAttribute<string>(log, "log_reference_id")?.replace(
|
||||||
/"/g,
|
/"/g,
|
||||||
'',
|
"",
|
||||||
) || null
|
) || null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -301,7 +301,7 @@ export function hasCliLogOneOfMultipleSpans(
|
||||||
|
|
||||||
export interface CliLogStartedSyncingMoneroWallet extends CliLog {
|
export interface CliLogStartedSyncingMoneroWallet extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Syncing Monero wallet';
|
message: "Syncing Monero wallet";
|
||||||
current_sync_height?: boolean;
|
current_sync_height?: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -309,18 +309,18 @@ export interface CliLogStartedSyncingMoneroWallet extends CliLog {
|
||||||
export function isCliLogStartedSyncingMoneroWallet(
|
export function isCliLogStartedSyncingMoneroWallet(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogStartedSyncingMoneroWallet {
|
): log is CliLogStartedSyncingMoneroWallet {
|
||||||
return log.fields.message === 'Syncing Monero wallet';
|
return log.fields.message === "Syncing Monero wallet";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogFinishedSyncingMoneroWallet extends CliLog {
|
export interface CliLogFinishedSyncingMoneroWallet extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Synced Monero wallet';
|
message: "Synced Monero wallet";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogFailedToSyncMoneroWallet extends CliLog {
|
export interface CliLogFailedToSyncMoneroWallet extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Failed to sync Monero wallet';
|
message: "Failed to sync Monero wallet";
|
||||||
error: string;
|
error: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -328,18 +328,18 @@ export interface CliLogFailedToSyncMoneroWallet extends CliLog {
|
||||||
export function isCliLogFailedToSyncMoneroWallet(
|
export function isCliLogFailedToSyncMoneroWallet(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogFailedToSyncMoneroWallet {
|
): log is CliLogFailedToSyncMoneroWallet {
|
||||||
return log.fields.message === 'Failed to sync Monero wallet';
|
return log.fields.message === "Failed to sync Monero wallet";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLogFinishedSyncingMoneroWallet(
|
export function isCliLogFinishedSyncingMoneroWallet(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogFinishedSyncingMoneroWallet {
|
): log is CliLogFinishedSyncingMoneroWallet {
|
||||||
return log.fields.message === 'Monero wallet synced';
|
return log.fields.message === "Monero wallet synced";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
|
export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Downloading monero-wallet-rpc';
|
message: "Downloading monero-wallet-rpc";
|
||||||
progress: string;
|
progress: string;
|
||||||
size: string;
|
size: string;
|
||||||
download_url: string;
|
download_url: string;
|
||||||
|
@ -349,19 +349,19 @@ export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
|
||||||
export function isCliLogDownloadingMoneroWalletRpc(
|
export function isCliLogDownloadingMoneroWalletRpc(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogDownloadingMoneroWalletRpc {
|
): log is CliLogDownloadingMoneroWalletRpc {
|
||||||
return log.fields.message === 'Downloading monero-wallet-rpc';
|
return log.fields.message === "Downloading monero-wallet-rpc";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogStartedSyncingMoneroWallet extends CliLog {
|
export interface CliLogStartedSyncingMoneroWallet extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Syncing Monero wallet';
|
message: "Syncing Monero wallet";
|
||||||
current_sync_height?: boolean;
|
current_sync_height?: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
|
export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Downloading monero-wallet-rpc';
|
message: "Downloading monero-wallet-rpc";
|
||||||
progress: string;
|
progress: string;
|
||||||
size: string;
|
size: string;
|
||||||
download_url: string;
|
download_url: string;
|
||||||
|
@ -370,7 +370,7 @@ export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
|
||||||
|
|
||||||
export interface CliLogGotNotificationForNewBlock extends CliLog {
|
export interface CliLogGotNotificationForNewBlock extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Got notification for new block';
|
message: "Got notification for new block";
|
||||||
block_height: string;
|
block_height: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -378,29 +378,36 @@ export interface CliLogGotNotificationForNewBlock extends CliLog {
|
||||||
export function isCliLogGotNotificationForNewBlock(
|
export function isCliLogGotNotificationForNewBlock(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogGotNotificationForNewBlock {
|
): 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 {
|
export interface CliLogAttemptingToCooperativelyRedeemXmr extends CliLog {
|
||||||
fields: {
|
fields: {
|
||||||
message: 'Attempting to cooperatively redeem XMR after being punished';
|
message: "Attempting to cooperatively redeem XMR after being punished";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLogAttemptingToCooperativelyRedeemXmr(
|
export function isCliLogAttemptingToCooperativelyRedeemXmr(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogAttemptingToCooperativelyRedeemXmr {
|
): 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: {
|
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(
|
export function isCliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr(
|
||||||
log: CliLog,
|
log: CliLog,
|
||||||
): log is CliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr {
|
): 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 { CliLog, SwapSpawnType } from "./cliModel";
|
||||||
import { Provider } from './apiModel';
|
import { Provider } from "./apiModel";
|
||||||
|
|
||||||
export interface SwapSlice {
|
export interface SwapSlice {
|
||||||
state: SwapState | null;
|
state: SwapState | null;
|
||||||
|
@ -20,21 +20,21 @@ export interface SwapState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SwapStateType {
|
export enum SwapStateType {
|
||||||
INITIATED = 'initiated',
|
INITIATED = "initiated",
|
||||||
RECEIVED_QUOTE = 'received quote',
|
RECEIVED_QUOTE = "received quote",
|
||||||
WAITING_FOR_BTC_DEPOSIT = 'waiting for btc deposit',
|
WAITING_FOR_BTC_DEPOSIT = "waiting for btc deposit",
|
||||||
STARTED = 'started',
|
STARTED = "started",
|
||||||
BTC_LOCK_TX_IN_MEMPOOL = 'btc lock tx is in mempool',
|
BTC_LOCK_TX_IN_MEMPOOL = "btc lock tx is in mempool",
|
||||||
XMR_LOCK_TX_IN_MEMPOOL = 'xmr lock tx is in mempool',
|
XMR_LOCK_TX_IN_MEMPOOL = "xmr lock tx is in mempool",
|
||||||
XMR_LOCKED = 'xmr is locked',
|
XMR_LOCKED = "xmr is locked",
|
||||||
BTC_REDEEMED = 'btc redeemed',
|
BTC_REDEEMED = "btc redeemed",
|
||||||
XMR_REDEEM_IN_MEMPOOL = 'xmr redeem tx is in mempool',
|
XMR_REDEEM_IN_MEMPOOL = "xmr redeem tx is in mempool",
|
||||||
PROCESS_EXITED = 'process exited',
|
PROCESS_EXITED = "process exited",
|
||||||
BTC_CANCELLED = 'btc cancelled',
|
BTC_CANCELLED = "btc cancelled",
|
||||||
BTC_REFUNDED = 'btc refunded',
|
BTC_REFUNDED = "btc refunded",
|
||||||
BTC_PUNISHED = 'btc punished',
|
BTC_PUNISHED = "btc punished",
|
||||||
ATTEMPTING_COOPERATIVE_REDEEM = 'attempting cooperative redeem',
|
ATTEMPTING_COOPERATIVE_REDEEM = "attempting cooperative redeem",
|
||||||
COOPERATIVE_REDEEM_REJECTED = 'cooperative redeem rejected',
|
COOPERATIVE_REDEEM_REJECTED = "cooperative redeem rejected",
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSwapState(state?: SwapState | null): state is SwapState {
|
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<
|
export async function fetchProvidersViaHttp(): Promise<
|
||||||
ExtendedProviderStatus[]
|
ExtendedProviderStatus[]
|
||||||
|
@ -23,9 +23,9 @@ export async function submitFeedbackViaHttp(
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/api/submit-feedback`, {
|
const response = await fetch(`${API_BASE_URL}/api/submit-feedback`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ body, attachedData }),
|
body: JSON.stringify({ body, attachedData }),
|
||||||
});
|
});
|
||||||
|
@ -53,9 +53,9 @@ async function fetchCurrencyUsdPrice(currency: string): Promise<number> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchBtcPrice(): Promise<number> {
|
export async function fetchBtcPrice(): Promise<number> {
|
||||||
return fetchCurrencyUsdPrice('bitcoin');
|
return fetchCurrencyUsdPrice("bitcoin");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchXmrPrice(): Promise<number> {
|
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 { Box, makeStyles, CssBaseline } from "@material-ui/core";
|
||||||
import { createTheme, ThemeProvider } from '@material-ui/core/styles';
|
import { createTheme, ThemeProvider } from "@material-ui/core/styles";
|
||||||
import { indigo } from '@material-ui/core/colors';
|
import { indigo } from "@material-ui/core/colors";
|
||||||
import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
|
import { MemoryRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
import Navigation, { drawerWidth } from './navigation/Navigation';
|
import Navigation, { drawerWidth } from "./navigation/Navigation";
|
||||||
import HistoryPage from './pages/history/HistoryPage';
|
import HistoryPage from "./pages/history/HistoryPage";
|
||||||
import SwapPage from './pages/swap/SwapPage';
|
import SwapPage from "./pages/swap/SwapPage";
|
||||||
import WalletPage from './pages/wallet/WalletPage';
|
import WalletPage from "./pages/wallet/WalletPage";
|
||||||
import HelpPage from './pages/help/HelpPage';
|
import HelpPage from "./pages/help/HelpPage";
|
||||||
import GlobalSnackbarProvider from './snackbar/GlobalSnackbarProvider';
|
import GlobalSnackbarProvider from "./snackbar/GlobalSnackbarProvider";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
innerContent: {
|
innerContent: {
|
||||||
|
@ -20,14 +20,14 @@ const useStyles = makeStyles((theme) => ({
|
||||||
|
|
||||||
const theme = createTheme({
|
const theme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
type: 'dark',
|
type: "dark",
|
||||||
primary: {
|
primary: {
|
||||||
main: '#f4511e',
|
main: "#f4511e",
|
||||||
},
|
},
|
||||||
secondary: indigo,
|
secondary: indigo,
|
||||||
},
|
},
|
||||||
transitions: {
|
transitions: {
|
||||||
create: () => 'none',
|
create: () => "none",
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
MuiButtonBase: {
|
MuiButtonBase: {
|
||||||
|
|
|
@ -4,12 +4,12 @@ import {
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
IconButton,
|
IconButton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@material-ui/core';
|
} from "@material-ui/core";
|
||||||
import { ReactElement, ReactNode, useEffect, useState } from 'react';
|
import { ReactElement, ReactNode, useEffect, useState } from "react";
|
||||||
import { useSnackbar } from 'notistack';
|
import { useSnackbar } from "notistack";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import { RpcProcessStateType } from 'models/rpcModel';
|
import { RpcProcessStateType } from "models/rpcModel";
|
||||||
import { isExternalRpc } from 'store/config';
|
import { isExternalRpc } from "store/config";
|
||||||
|
|
||||||
function IpcButtonTooltip({
|
function IpcButtonTooltip({
|
||||||
requiresRpcAndNotReady,
|
requiresRpcAndNotReady,
|
||||||
|
@ -27,19 +27,19 @@ function IpcButtonTooltip({
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMessage = () => {
|
const getMessage = () => {
|
||||||
if (!requiresRpcAndNotReady) return '';
|
if (!requiresRpcAndNotReady) return "";
|
||||||
|
|
||||||
switch (processType) {
|
switch (processType) {
|
||||||
case RpcProcessStateType.LISTENING_FOR_CONNECTIONS:
|
case RpcProcessStateType.LISTENING_FOR_CONNECTIONS:
|
||||||
return '';
|
return "";
|
||||||
case RpcProcessStateType.STARTED:
|
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:
|
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:
|
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:
|
default:
|
||||||
return '';
|
return "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -108,13 +108,13 @@ export default function IpcInvokeButton<T>({
|
||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
try {
|
try {
|
||||||
// const result = await ipcRenderer.invoke(ipcChannel, ...ipcArgs);
|
// const result = await ipcRenderer.invoke(ipcChannel, ...ipcArgs);
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
// onSuccess?.(result);
|
// onSuccess?.(result);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (displayErrorSnackbar) {
|
if (displayErrorSnackbar) {
|
||||||
enqueueSnackbar((e as Error).message, {
|
enqueueSnackbar((e as Error).message, {
|
||||||
autoHideDuration: 60 * 1000,
|
autoHideDuration: 60 * 1000,
|
||||||
variant: 'error',
|
variant: "error",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { ReactNode, useEffect, useState } from "react";
|
||||||
interface IpcInvokeButtonProps<T> {
|
interface IpcInvokeButtonProps<T> {
|
||||||
onSuccess?: (data: T) => void;
|
onSuccess?: (data: T) => void;
|
||||||
onClick: () => Promise<T>;
|
onClick: () => Promise<T>;
|
||||||
onPendingChange?: (bool) => void;
|
onPendingChange?: (isPending: boolean) => void;
|
||||||
isLoadingOverride?: boolean;
|
isLoadingOverride?: boolean;
|
||||||
isIconButton?: boolean;
|
isIconButton?: boolean;
|
||||||
loadIcon?: ReactNode;
|
loadIcon?: ReactNode;
|
||||||
|
@ -46,7 +46,7 @@ export default function PromiseInvokeButton<T>({
|
||||||
onSuccess?.(result);
|
onSuccess?.(result);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (displayErrorSnackbar) {
|
if (displayErrorSnackbar) {
|
||||||
enqueueSnackbar((e as Error).message, {
|
enqueueSnackbar(e as String, {
|
||||||
autoHideDuration: 60 * 1000,
|
autoHideDuration: 60 * 1000,
|
||||||
variant: "error",
|
variant: "error",
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Button } from '@material-ui/core';
|
import { Button } from "@material-ui/core";
|
||||||
import Alert from '@material-ui/lab/Alert';
|
import Alert from "@material-ui/lab/Alert";
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
|
|
||||||
export default function FundsLeftInWalletAlert() {
|
export default function FundsLeftInWalletAlert() {
|
||||||
const fundsLeft = useAppSelector((state) => state.rpc.state.balance);
|
const fundsLeft = useAppSelector((state) => state.rpc.state.balance);
|
||||||
|
@ -16,7 +16,7 @@ export default function FundsLeftInWalletAlert() {
|
||||||
<Button
|
<Button
|
||||||
color="inherit"
|
color="inherit"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => navigate('/wallet')}
|
onClick={() => navigate("/wallet")}
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from "@material-ui/lab";
|
||||||
import { Box, LinearProgress } from '@material-ui/core';
|
import { Box, LinearProgress } from "@material-ui/core";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
|
|
||||||
export default function MoneroWalletRpcUpdatingAlert() {
|
export default function MoneroWalletRpcUpdatingAlert() {
|
||||||
const updateState = useAppSelector(
|
const updateState = useAppSelector(
|
||||||
|
@ -17,7 +17,7 @@ export default function MoneroWalletRpcUpdatingAlert() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert severity="info">
|
<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>
|
<span>The Monero wallet is updating. This may take a few moments</span>
|
||||||
<LinearProgress
|
<LinearProgress
|
||||||
variant="determinate"
|
variant="determinate"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from "@material-ui/lab";
|
||||||
import { Box, makeStyles } from '@material-ui/core';
|
import { Box, makeStyles } from "@material-ui/core";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import WalletRefreshButton from '../pages/wallet/WalletRefreshButton';
|
import WalletRefreshButton from "../pages/wallet/WalletRefreshButton";
|
||||||
import { SatsAmount } from '../other/Units';
|
import { SatsAmount } from "../other/Units";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from "@material-ui/lab";
|
||||||
import { CircularProgress } from '@material-ui/core';
|
import { CircularProgress } from "@material-ui/core";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import { RpcProcessStateType } from 'models/rpcModel';
|
import { RpcProcessStateType } from "models/rpcModel";
|
||||||
|
|
||||||
export default function RpcStatusAlert() {
|
export default function RpcStatusAlert() {
|
||||||
const rpcProcess = useAppSelector((s) => s.rpc.process);
|
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 { Alert, AlertTitle } from "@material-ui/lab";
|
||||||
import { useActiveSwapInfo } from "store/hooks";
|
import { useActiveSwapInfo } from "store/hooks";
|
||||||
import {
|
import {
|
||||||
isSwapTimelockInfoCancelled,
|
isSwapTimelockInfoCancelled,
|
||||||
isSwapTimelockInfoNone,
|
isSwapTimelockInfoNone,
|
||||||
} from "models/rpcModel";
|
} from "models/rpcModel";
|
||||||
import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
|
import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
marginBottom: theme.spacing(1),
|
marginBottom: theme.spacing(1),
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
margin: theme.spacing(0.25),
|
margin: theme.spacing(0.25),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function SwapMightBeCancelledAlert({
|
export default function SwapMightBeCancelledAlert({
|
||||||
bobBtcLockTxConfirmations,
|
bobBtcLockTxConfirmations,
|
||||||
}: {
|
}: {
|
||||||
bobBtcLockTxConfirmations: number;
|
bobBtcLockTxConfirmations: number;
|
||||||
}) {
|
}) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const swap = useActiveSwapInfo();
|
const swap = useActiveSwapInfo();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
bobBtcLockTxConfirmations < 5 ||
|
bobBtcLockTxConfirmations < 5 ||
|
||||||
swap === null ||
|
swap === null ||
|
||||||
swap.timelock === null
|
swap.timelock === null
|
||||||
) {
|
) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { timelock } = swap;
|
const { timelock } = swap;
|
||||||
const punishTimelockOffset = swap.punish_timelock;
|
const punishTimelockOffset = swap.punish_timelock;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert severity="warning" className={classes.outer} variant="filled">
|
<Alert severity="warning" className={classes.outer} variant="filled">
|
||||||
<AlertTitle>Be careful!</AlertTitle>
|
<AlertTitle>Be careful!</AlertTitle>
|
||||||
The swap provider has taken a long time to lock their Monero. This
|
The swap provider has taken a long time to lock their Monero. This might
|
||||||
might mean that:
|
mean that:
|
||||||
<ul className={classes.list}>
|
<ul className={classes.list}>
|
||||||
<li>
|
<li>
|
||||||
There is a technical issue that prevents them from locking
|
There is a technical issue that prevents them from locking their funds
|
||||||
their funds
|
</li>
|
||||||
</li>
|
<li>They are a malicious actor (unlikely)</li>
|
||||||
<li>They are a malicious actor (unlikely)</li>
|
</ul>
|
||||||
</ul>
|
<br />
|
||||||
<br />
|
There is still hope for the swap to be successful but you have to be extra
|
||||||
There is still hope for the swap to be successful but you have to be
|
careful. Regardless of why it has taken them so long, it is important that
|
||||||
extra careful. Regardless of why it has taken them so long, it is
|
you refund the swap within the required time period if the swap is not
|
||||||
important that you refund the swap within the required time period
|
completed. If you fail to to do so, you will be punished and lose your
|
||||||
if the swap is not completed. If you fail to to do so, you will be
|
money.
|
||||||
punished and lose your money.
|
<ul className={classes.list}>
|
||||||
<ul className={classes.list}>
|
{isSwapTimelockInfoNone(timelock) && (
|
||||||
{isSwapTimelockInfoNone(timelock) && (
|
<>
|
||||||
<>
|
<li>
|
||||||
<li>
|
<strong>
|
||||||
<strong>
|
You will be able to refund in about{" "}
|
||||||
You will be able to refund in about{" "}
|
<HumanizedBitcoinBlockDuration
|
||||||
<HumanizedBitcoinBlockDuration
|
blocks={timelock.None.blocks_left}
|
||||||
blocks={timelock.None.blocks_left}
|
/>
|
||||||
/>
|
</strong>
|
||||||
</strong>
|
</li>
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<strong>
|
<strong>
|
||||||
If you have not refunded or completed the swap
|
If you have not refunded or completed the swap in about{" "}
|
||||||
in about{" "}
|
<HumanizedBitcoinBlockDuration
|
||||||
<HumanizedBitcoinBlockDuration
|
blocks={timelock.None.blocks_left + punishTimelockOffset}
|
||||||
blocks={
|
/>
|
||||||
timelock.None.blocks_left +
|
, you will lose your funds.
|
||||||
punishTimelockOffset
|
</strong>
|
||||||
}
|
</li>
|
||||||
/>
|
</>
|
||||||
, you will lose your funds.
|
)}
|
||||||
</strong>
|
{isSwapTimelockInfoCancelled(timelock) && (
|
||||||
</li>
|
<li>
|
||||||
</>
|
<strong>
|
||||||
)}
|
If you have not refunded or completed the swap in about{" "}
|
||||||
{isSwapTimelockInfoCancelled(timelock) && (
|
<HumanizedBitcoinBlockDuration
|
||||||
<li>
|
blocks={timelock.Cancel.blocks_left}
|
||||||
<strong>
|
/>
|
||||||
If you have not refunded or completed the swap in
|
, you will lose your funds.
|
||||||
about{" "}
|
</strong>
|
||||||
<HumanizedBitcoinBlockDuration
|
</li>
|
||||||
blocks={timelock.Cancel.blocks_left}
|
)}
|
||||||
/>
|
<li>
|
||||||
, you will lose your funds.
|
As long as you see this screen, the swap will be refunded
|
||||||
</strong>
|
automatically when the time comes. If this fails, you have to manually
|
||||||
</li>
|
refund by navigating to the History page.
|
||||||
)}
|
</li>
|
||||||
<li>
|
</ul>
|
||||||
As long as you see this screen, the swap will be refunded
|
</Alert>
|
||||||
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 { ReactNode } from "react";
|
||||||
import { exhaustiveGuard } from "utils/typescriptUtils";
|
import { exhaustiveGuard } from "utils/typescriptUtils";
|
||||||
import {
|
import {
|
||||||
SwapCancelRefundButton,
|
SwapCancelRefundButton,
|
||||||
SwapResumeButton,
|
SwapResumeButton,
|
||||||
} from "../pages/history/table/HistoryRowActions";
|
} from "../pages/history/table/HistoryRowActions";
|
||||||
import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
|
import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
|
||||||
import {
|
import {
|
||||||
GetSwapInfoResponse,
|
GetSwapInfoResponse,
|
||||||
GetSwapInfoResponseRunningSwap,
|
GetSwapInfoResponseRunningSwap,
|
||||||
isGetSwapInfoResponseRunningSwap,
|
isGetSwapInfoResponseRunningSwap,
|
||||||
isSwapTimelockInfoCancelled,
|
isSwapTimelockInfoCancelled,
|
||||||
isSwapTimelockInfoNone,
|
isSwapTimelockInfoNone,
|
||||||
isSwapTimelockInfoPunished,
|
isSwapTimelockInfoPunished,
|
||||||
SwapStateName,
|
SwapStateName,
|
||||||
SwapTimelockInfoCancelled,
|
SwapTimelockInfoCancelled,
|
||||||
SwapTimelockInfoNone,
|
SwapTimelockInfoNone,
|
||||||
} from "../../../models/rpcModel";
|
} from "../../../models/rpcModel";
|
||||||
import { SwapMoneroRecoveryButton } from "../pages/history/table/SwapMoneroRecoveryButton";
|
import { SwapMoneroRecoveryButton } from "../pages/history/table/SwapMoneroRecoveryButton";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
box: {
|
box: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: "0.5rem",
|
gap: "0.5rem",
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
padding: "0px",
|
padding: "0px",
|
||||||
margin: "0px",
|
margin: "0px",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,15 +38,15 @@ const useStyles = makeStyles({
|
||||||
* @returns JSX.Element
|
* @returns JSX.Element
|
||||||
*/
|
*/
|
||||||
const MessageList = ({ messages }: { messages: ReactNode[] }) => {
|
const MessageList = ({ messages }: { messages: ReactNode[] }) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
return (
|
return (
|
||||||
<ul className={classes.list}>
|
<ul className={classes.list}>
|
||||||
{messages.map((msg, i) => (
|
{messages.map((msg, i) => (
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
<li key={i}>{msg}</li>
|
<li key={i}>{msg}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,24 +55,20 @@ const MessageList = ({ messages }: { messages: ReactNode[] }) => {
|
||||||
* @returns JSX.Element
|
* @returns JSX.Element
|
||||||
*/
|
*/
|
||||||
const BitcoinRedeemedStateAlert = ({ swap }: { swap: GetSwapInfoResponse }) => {
|
const BitcoinRedeemedStateAlert = ({ swap }: { swap: GetSwapInfoResponse }) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
return (
|
return (
|
||||||
<Box className={classes.box}>
|
<Box className={classes.box}>
|
||||||
<MessageList
|
<MessageList
|
||||||
messages={[
|
messages={[
|
||||||
"The Bitcoin has been redeemed by the other party",
|
"The Bitcoin has been redeemed by the other party",
|
||||||
"There is no risk of losing funds. You can take your time",
|
"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",
|
"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",
|
"If this step fails, you can manually redeem the funds",
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<SwapMoneroRecoveryButton
|
<SwapMoneroRecoveryButton swap={swap} size="small" variant="contained" />
|
||||||
swap={swap}
|
</Box>
|
||||||
size="small"
|
);
|
||||||
variant="contained"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,31 +78,28 @@ const BitcoinRedeemedStateAlert = ({ swap }: { swap: GetSwapInfoResponse }) => {
|
||||||
* @returns JSX.Element
|
* @returns JSX.Element
|
||||||
*/
|
*/
|
||||||
const BitcoinLockedNoTimelockExpiredStateAlert = ({
|
const BitcoinLockedNoTimelockExpiredStateAlert = ({
|
||||||
timelock,
|
timelock,
|
||||||
punishTimelockOffset,
|
punishTimelockOffset,
|
||||||
}: {
|
}: {
|
||||||
timelock: SwapTimelockInfoNone;
|
timelock: SwapTimelockInfoNone;
|
||||||
punishTimelockOffset: number;
|
punishTimelockOffset: number;
|
||||||
}) => (
|
}) => (
|
||||||
<MessageList
|
<MessageList
|
||||||
messages={[
|
messages={[
|
||||||
<>
|
<>
|
||||||
Your Bitcoin is locked. If the swap is not completed in
|
Your Bitcoin is locked. If the swap is not completed in approximately{" "}
|
||||||
approximately{" "}
|
<HumanizedBitcoinBlockDuration blocks={timelock.None.blocks_left} />,
|
||||||
<HumanizedBitcoinBlockDuration
|
you need to refund
|
||||||
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
|
||||||
You will lose your funds if you do not refund or complete the
|
blocks={timelock.None.blocks_left + punishTimelockOffset}
|
||||||
swap within{" "}
|
/>
|
||||||
<HumanizedBitcoinBlockDuration
|
</>,
|
||||||
blocks={timelock.None.blocks_left + punishTimelockOffset}
|
]}
|
||||||
/>
|
/>
|
||||||
</>,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,34 +110,30 @@ const BitcoinLockedNoTimelockExpiredStateAlert = ({
|
||||||
* @returns JSX.Element
|
* @returns JSX.Element
|
||||||
*/
|
*/
|
||||||
const BitcoinPossiblyCancelledAlert = ({
|
const BitcoinPossiblyCancelledAlert = ({
|
||||||
swap,
|
swap,
|
||||||
timelock,
|
timelock,
|
||||||
}: {
|
}: {
|
||||||
swap: GetSwapInfoResponse;
|
swap: GetSwapInfoResponse;
|
||||||
timelock: SwapTimelockInfoCancelled;
|
timelock: SwapTimelockInfoCancelled;
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
return (
|
return (
|
||||||
<Box className={classes.box}>
|
<Box className={classes.box}>
|
||||||
<MessageList
|
<MessageList
|
||||||
messages={[
|
messages={[
|
||||||
"The swap was cancelled because it did not complete in time",
|
"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 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{" "}
|
You will lose your funds if you do not refund within{" "}
|
||||||
<HumanizedBitcoinBlockDuration
|
<HumanizedBitcoinBlockDuration
|
||||||
blocks={timelock.Cancel.blocks_left}
|
blocks={timelock.Cancel.blocks_left}
|
||||||
/>
|
|
||||||
</>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
<SwapCancelRefundButton
|
</>,
|
||||||
swap={swap}
|
]}
|
||||||
size="small"
|
/>
|
||||||
variant="contained"
|
<SwapCancelRefundButton swap={swap} size="small" variant="contained" />
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,7 +141,7 @@ const BitcoinPossiblyCancelledAlert = ({
|
||||||
* @returns JSX.Element
|
* @returns JSX.Element
|
||||||
*/
|
*/
|
||||||
const ImmediateActionAlert = () => (
|
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
|
* @returns JSX.Element | null
|
||||||
*/
|
*/
|
||||||
function SwapAlertStatusText({
|
function SwapAlertStatusText({
|
||||||
swap,
|
swap,
|
||||||
}: {
|
}: {
|
||||||
swap: GetSwapInfoResponseRunningSwap;
|
swap: GetSwapInfoResponseRunningSwap;
|
||||||
}) {
|
}) {
|
||||||
switch (swap.state_name) {
|
switch (swap.state_name) {
|
||||||
// This is the state where the swap is safe because the other party has redeemed the Bitcoin
|
// This is the state where the swap is safe because the other party has redeemed the Bitcoin
|
||||||
// It cannot be punished anymore
|
// It cannot be punished anymore
|
||||||
case SwapStateName.BtcRedeemed:
|
case SwapStateName.BtcRedeemed:
|
||||||
return <BitcoinRedeemedStateAlert swap={swap} />;
|
return <BitcoinRedeemedStateAlert swap={swap} />;
|
||||||
|
|
||||||
// These are states that are at risk of punishment because the Bitcoin have been locked
|
// 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
|
// but has not been redeemed yet by the other party
|
||||||
case SwapStateName.BtcLocked:
|
case SwapStateName.BtcLocked:
|
||||||
case SwapStateName.XmrLockProofReceived:
|
case SwapStateName.XmrLockProofReceived:
|
||||||
case SwapStateName.XmrLocked:
|
case SwapStateName.XmrLocked:
|
||||||
case SwapStateName.EncSigSent:
|
case SwapStateName.EncSigSent:
|
||||||
case SwapStateName.CancelTimelockExpired:
|
case SwapStateName.CancelTimelockExpired:
|
||||||
case SwapStateName.BtcCancelled:
|
case SwapStateName.BtcCancelled:
|
||||||
if (swap.timelock !== null) {
|
if (swap.timelock !== null) {
|
||||||
if (isSwapTimelockInfoNone(swap.timelock)) {
|
if (isSwapTimelockInfoNone(swap.timelock)) {
|
||||||
return (
|
return (
|
||||||
<BitcoinLockedNoTimelockExpiredStateAlert
|
<BitcoinLockedNoTimelockExpiredStateAlert
|
||||||
punishTimelockOffset={swap.punish_timelock}
|
punishTimelockOffset={swap.punish_timelock}
|
||||||
timelock={swap.timelock}
|
timelock={swap.timelock}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSwapTimelockInfoCancelled(swap.timelock)) {
|
if (isSwapTimelockInfoCancelled(swap.timelock)) {
|
||||||
return (
|
return (
|
||||||
<BitcoinPossiblyCancelledAlert
|
<BitcoinPossiblyCancelledAlert
|
||||||
timelock={swap.timelock}
|
timelock={swap.timelock}
|
||||||
swap={swap}
|
swap={swap}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSwapTimelockInfoPunished(swap.timelock)) {
|
if (isSwapTimelockInfoPunished(swap.timelock)) {
|
||||||
return <ImmediateActionAlert />;
|
return <ImmediateActionAlert />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have covered all possible timelock states above
|
// We have covered all possible timelock states above
|
||||||
// If we reach this point, it means we have missed a case
|
// If we reach this point, it means we have missed a case
|
||||||
return exhaustiveGuard(swap.timelock);
|
return exhaustiveGuard(swap.timelock);
|
||||||
}
|
}
|
||||||
return <ImmediateActionAlert />;
|
return <ImmediateActionAlert />;
|
||||||
default:
|
default:
|
||||||
return exhaustiveGuard(swap.state_name);
|
return exhaustiveGuard(swap.state_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -218,27 +207,27 @@ function SwapAlertStatusText({
|
||||||
* @returns JSX.Element | null
|
* @returns JSX.Element | null
|
||||||
*/
|
*/
|
||||||
export default function SwapStatusAlert({
|
export default function SwapStatusAlert({
|
||||||
swap,
|
swap,
|
||||||
}: {
|
}: {
|
||||||
swap: GetSwapInfoResponse;
|
swap: GetSwapInfoResponse;
|
||||||
}): JSX.Element | null {
|
}): JSX.Element | null {
|
||||||
// If the swap is not running, there is no need to display the alert
|
// 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)
|
// 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)) {
|
if (!isGetSwapInfoResponseRunningSwap(swap)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
key={swap.swap_id}
|
key={swap.swap_id}
|
||||||
severity="warning"
|
severity="warning"
|
||||||
action={<SwapResumeButton swap={swap} />}
|
action={<SwapResumeButton swap={swap} />}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
>
|
>
|
||||||
<AlertTitle>
|
<AlertTitle>
|
||||||
Swap {swap.swap_id.substring(0, 5)}... is unfinished
|
Swap {swap.swap_id.substring(0, 5)}... is unfinished
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
<SwapAlertStatusText swap={swap} />
|
<SwapAlertStatusText swap={swap} />
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,26 +3,26 @@ import { useSwapInfosSortedByDate } from "store/hooks";
|
||||||
import SwapStatusAlert from "./SwapStatusAlert";
|
import SwapStatusAlert from "./SwapStatusAlert";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function SwapTxLockAlertsBox() {
|
export default function SwapTxLockAlertsBox() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
// We specifically choose ALL swaps here
|
// 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)
|
// 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
|
// the SwapStatusAlert component will not render an Alert
|
||||||
const swaps = useSwapInfosSortedByDate();
|
const swaps = useSwapInfosSortedByDate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={classes.outer}>
|
<Box className={classes.outer}>
|
||||||
{swaps.map((swap) => (
|
{swaps.map((swap) => (
|
||||||
<SwapStatusAlert key={swap.swap_id} swap={swap} />
|
<SwapStatusAlert key={swap.swap_id} swap={swap} />
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Button } from '@material-ui/core';
|
import { Button } from "@material-ui/core";
|
||||||
import Alert from '@material-ui/lab/Alert';
|
import Alert from "@material-ui/lab/Alert";
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useResumeableSwapsCount } from 'store/hooks';
|
import { useResumeableSwapsCount } from "store/hooks";
|
||||||
|
|
||||||
export default function UnfinishedSwapsAlert() {
|
export default function UnfinishedSwapsAlert() {
|
||||||
const resumableSwapsCount = useResumeableSwapsCount();
|
const resumableSwapsCount = useResumeableSwapsCount();
|
||||||
|
@ -16,16 +16,16 @@ export default function UnfinishedSwapsAlert() {
|
||||||
<Button
|
<Button
|
||||||
color="inherit"
|
color="inherit"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => navigate('/history')}
|
onClick={() => navigate("/history")}
|
||||||
>
|
>
|
||||||
VIEW
|
VIEW
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
You have{' '}
|
You have{" "}
|
||||||
{resumableSwapsCount > 1
|
{resumableSwapsCount > 1
|
||||||
? `${resumableSwapsCount} unfinished swaps`
|
? `${resumableSwapsCount} unfinished swaps`
|
||||||
: 'one unfinished swap'}
|
: "one unfinished swap"}
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { SvgIcon } from '@material-ui/core';
|
import { SvgIcon } from "@material-ui/core";
|
||||||
import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon';
|
import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
|
||||||
|
|
||||||
export default function BitcoinIcon(props: SvgIconProps) {
|
export default function BitcoinIcon(props: SvgIconProps) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon';
|
import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
|
||||||
import { SvgIcon } from '@material-ui/core';
|
import { SvgIcon } from "@material-ui/core";
|
||||||
|
|
||||||
export default function DiscordIcon(props: SvgIconProps) {
|
export default function DiscordIcon(props: SvgIconProps) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from "react";
|
||||||
import { IconButton } from '@material-ui/core';
|
import { IconButton } from "@material-ui/core";
|
||||||
|
|
||||||
export default function LinkIconButton({
|
export default function LinkIconButton({
|
||||||
url,
|
url,
|
||||||
|
@ -9,7 +9,7 @@ export default function LinkIconButton({
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<IconButton component="span" onClick={() => window.open(url, '_blank')}>
|
<IconButton component="span" onClick={() => window.open(url, "_blank")}>
|
||||||
{children}
|
{children}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { SvgIcon } from '@material-ui/core';
|
import { SvgIcon } from "@material-ui/core";
|
||||||
import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon';
|
import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
|
||||||
|
|
||||||
export default function MoneroIcon(props: SvgIconProps) {
|
export default function MoneroIcon(props: SvgIconProps) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { SvgIcon } from '@material-ui/core';
|
import { SvgIcon } from "@material-ui/core";
|
||||||
import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon';
|
import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
|
||||||
|
|
||||||
export default function TorIcon(props: SvgIconProps) {
|
export default function TorIcon(props: SvgIconProps) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from "react";
|
||||||
import { TextField } from '@material-ui/core';
|
import { TextField } from "@material-ui/core";
|
||||||
import { TextFieldProps } from '@material-ui/core/TextField/TextField';
|
import { TextFieldProps } from "@material-ui/core/TextField/TextField";
|
||||||
import { isBtcAddressValid } from 'utils/conversionUtils';
|
import { isBtcAddressValid } from "utils/conversionUtils";
|
||||||
import { isTestnet } from 'store/config';
|
import { isTestnet } from "store/config";
|
||||||
|
|
||||||
export default function BitcoinAddressTextField({
|
export default function BitcoinAddressTextField({
|
||||||
address,
|
address,
|
||||||
|
@ -16,11 +16,11 @@ export default function BitcoinAddressTextField({
|
||||||
onAddressValidityChange: (valid: boolean) => void;
|
onAddressValidityChange: (valid: boolean) => void;
|
||||||
helperText: string;
|
helperText: string;
|
||||||
} & TextFieldProps) {
|
} & TextFieldProps) {
|
||||||
const placeholder = isTestnet() ? 'tb1q4aelwalu...' : 'bc18ociqZ9mZ...';
|
const placeholder = isTestnet() ? "tb1q4aelwalu..." : "bc18ociqZ9mZ...";
|
||||||
const errorText = isBtcAddressValid(address, isTestnet())
|
const errorText = isBtcAddressValid(address, isTestnet())
|
||||||
? null
|
? null
|
||||||
: `Only bech32 addresses are supported. They begin with "${
|
: `Only bech32 addresses are supported. They begin with "${
|
||||||
isTestnet() ? 'tb1' : 'bc1'
|
isTestnet() ? "tb1" : "bc1"
|
||||||
}"`;
|
}"`;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from "react";
|
||||||
import { TextField } from '@material-ui/core';
|
import { TextField } from "@material-ui/core";
|
||||||
import { TextFieldProps } from '@material-ui/core/TextField/TextField';
|
import { TextFieldProps } from "@material-ui/core/TextField/TextField";
|
||||||
import { isXmrAddressValid } from 'utils/conversionUtils';
|
import { isXmrAddressValid } from "utils/conversionUtils";
|
||||||
import { isTestnet } from 'store/config';
|
import { isTestnet } from "store/config";
|
||||||
|
|
||||||
export default function MoneroAddressTextField({
|
export default function MoneroAddressTextField({
|
||||||
address,
|
address,
|
||||||
|
@ -16,10 +16,10 @@ export default function MoneroAddressTextField({
|
||||||
onAddressValidityChange: (valid: boolean) => void;
|
onAddressValidityChange: (valid: boolean) => void;
|
||||||
helperText: string;
|
helperText: string;
|
||||||
} & TextFieldProps) {
|
} & TextFieldProps) {
|
||||||
const placeholder = isTestnet() ? '59McWTPGc745...' : '888tNkZrPN6J...';
|
const placeholder = isTestnet() ? "59McWTPGc745..." : "888tNkZrPN6J...";
|
||||||
const errorText = isXmrAddressValid(address, isTestnet())
|
const errorText = isXmrAddressValid(address, isTestnet())
|
||||||
? null
|
? null
|
||||||
: 'Not a valid Monero address';
|
: "Not a valid Monero address";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onAddressValidityChange(!errorText);
|
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({
|
const useStyles = makeStyles({
|
||||||
root: {
|
root: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
justifyContent: 'space-between',
|
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) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
logsOuter: {
|
logsOuter: {
|
||||||
overflow: 'auto',
|
overflow: "auto",
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
marginTop: theme.spacing(1),
|
marginTop: theme.spacing(1),
|
||||||
marginBottom: theme.spacing(1),
|
marginBottom: theme.spacing(1),
|
||||||
maxHeight: '10rem',
|
maxHeight: "10rem",
|
||||||
},
|
},
|
||||||
copyButton: {
|
copyButton: {
|
||||||
marginTop: theme.spacing(1),
|
marginTop: theme.spacing(1),
|
||||||
|
@ -17,7 +17,7 @@ export default function PaperTextBox({ stdOut }: { stdOut: string }) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
function handleCopyLogs() {
|
function handleCopyLogs() {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,8 +5,8 @@ import {
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@material-ui/core';
|
} from "@material-ui/core";
|
||||||
import IpcInvokeButton from '../IpcInvokeButton';
|
import IpcInvokeButton from "../IpcInvokeButton";
|
||||||
|
|
||||||
type SwapCancelAlertProps = {
|
type SwapCancelAlertProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Select,
|
Select,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from "notistack";
|
||||||
|
@ -21,24 +21,24 @@ import { PiconeroAmount } from "../../other/Units";
|
||||||
import LoadingButton from "../../other/LoadingButton";
|
import LoadingButton from "../../other/LoadingButton";
|
||||||
|
|
||||||
async function submitFeedback(body: string, swapId: string | number) {
|
async function submitFeedback(body: string, swapId: string | number) {
|
||||||
let attachedBody = "";
|
let attachedBody = "";
|
||||||
|
|
||||||
if (swapId !== 0 && typeof swapId === "string") {
|
if (swapId !== 0 && typeof swapId === "string") {
|
||||||
const swapInfo = store.getState().rpc.state.swapInfos[swapId];
|
const swapInfo = store.getState().rpc.state.swapInfos[swapId];
|
||||||
const logs = [] as CliLog[];
|
const logs = [] as CliLog[];
|
||||||
|
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
|
|
||||||
if (swapInfo === undefined) {
|
if (swapInfo === undefined) {
|
||||||
throw new Error(`Swap with id ${swapId} not found`);
|
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")}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* selectedSwap = 0 means no swap is attached
|
||||||
*/
|
*/
|
||||||
function SwapSelectDropDown({
|
function SwapSelectDropDown({
|
||||||
selectedSwap,
|
selectedSwap,
|
||||||
setSelectedSwap,
|
setSelectedSwap,
|
||||||
}: {
|
}: {
|
||||||
selectedSwap: string | number;
|
selectedSwap: string | number;
|
||||||
setSelectedSwap: (swapId: string | number) => void;
|
setSelectedSwap: (swapId: string | number) => void;
|
||||||
}) {
|
}) {
|
||||||
const swaps = useAppSelector((state) =>
|
const swaps = useAppSelector((state) =>
|
||||||
Object.values(state.rpc.state.swapInfos),
|
Object.values(state.rpc.state.swapInfos),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
value={selectedSwap}
|
value={selectedSwap}
|
||||||
label="Attach logs"
|
label="Attach logs"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={(e) => setSelectedSwap(e.target.value as string)}
|
onChange={(e) => setSelectedSwap(e.target.value as string)}
|
||||||
>
|
>
|
||||||
<MenuItem value={0}>Do not attach logs</MenuItem>
|
<MenuItem value={0}>Do not attach logs</MenuItem>
|
||||||
{swaps.map((swap) => (
|
{swaps.map((swap) => (
|
||||||
<MenuItem value={swap.swap_id}>
|
<MenuItem value={swap.swap_id}>
|
||||||
Swap {swap.swap_id.substring(0, 5)}... from{" "}
|
Swap {swap.swap_id.substring(0, 5)}... from{" "}
|
||||||
{new Date(parseDateString(swap.start_date)).toDateString()}{" "}
|
{new Date(parseDateString(swap.start_date)).toDateString()} (
|
||||||
(
|
<PiconeroAmount amount={swap.xmr_amount} />)
|
||||||
<PiconeroAmount amount={swap.xmr_amount} />)
|
</MenuItem>
|
||||||
</MenuItem>
|
))}
|
||||||
))}
|
</Select>
|
||||||
</Select>
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_FEEDBACK_LENGTH = 4000;
|
const MAX_FEEDBACK_LENGTH = 4000;
|
||||||
|
|
||||||
export default function FeedbackDialog({
|
export default function FeedbackDialog({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [pending, setPending] = useState(false);
|
const [pending, setPending] = useState(false);
|
||||||
const [bodyText, setBodyText] = useState("");
|
const [bodyText, setBodyText] = useState("");
|
||||||
const currentSwapId = useActiveSwapInfo();
|
const currentSwapId = useActiveSwapInfo();
|
||||||
|
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
|
||||||
const [selectedAttachedSwap, setSelectedAttachedSwap] = useState<
|
const [selectedAttachedSwap, setSelectedAttachedSwap] = useState<
|
||||||
string | number
|
string | number
|
||||||
>(currentSwapId?.swap_id || 0);
|
>(currentSwapId?.swap_id || 0);
|
||||||
|
|
||||||
const bodyTooLong = bodyText.length > MAX_FEEDBACK_LENGTH;
|
const bodyTooLong = bodyText.length > MAX_FEEDBACK_LENGTH;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose}>
|
<Dialog open={open} onClose={onClose}>
|
||||||
<DialogTitle>Submit Feedback</DialogTitle>
|
<DialogTitle>Submit Feedback</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
Got something to say? Drop us a message below. If you had an
|
Got something to say? Drop us a message below. If you had an issue
|
||||||
issue with a specific swap, select it from the dropdown to
|
with a specific swap, select it from the dropdown to attach the logs.
|
||||||
attach the logs. It will help us figure out what went wrong.
|
It will help us figure out what went wrong. Hit that submit button
|
||||||
Hit that submit button when you are ready. We appreciate you
|
when you are ready. We appreciate you taking the time to share your
|
||||||
taking the time to share your thoughts!
|
thoughts!
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: "1rem",
|
gap: "1rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={bodyText}
|
value={bodyText}
|
||||||
onChange={(e) => setBodyText(e.target.value)}
|
onChange={(e) => setBodyText(e.target.value)}
|
||||||
label={
|
label={
|
||||||
bodyTooLong
|
bodyTooLong
|
||||||
? `Text is too long (${bodyText.length}/${MAX_FEEDBACK_LENGTH})`
|
? `Text is too long (${bodyText.length}/${MAX_FEEDBACK_LENGTH})`
|
||||||
: "Feedback"
|
: "Feedback"
|
||||||
}
|
}
|
||||||
multiline
|
multiline
|
||||||
minRows={4}
|
minRows={4}
|
||||||
maxRows={4}
|
maxRows={4}
|
||||||
fullWidth
|
fullWidth
|
||||||
error={bodyTooLong}
|
error={bodyTooLong}
|
||||||
/>
|
/>
|
||||||
<SwapSelectDropDown
|
<SwapSelectDropDown
|
||||||
selectedSwap={selectedAttachedSwap}
|
selectedSwap={selectedAttachedSwap}
|
||||||
setSelectedSwap={setSelectedAttachedSwap}
|
setSelectedSwap={setSelectedAttachedSwap}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (pending) {
|
if (pending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setPending(true);
|
setPending(true);
|
||||||
await submitFeedback(
|
await submitFeedback(bodyText, selectedAttachedSwap);
|
||||||
bodyText,
|
enqueueSnackbar("Feedback submitted successfully!", {
|
||||||
selectedAttachedSwap,
|
variant: "success",
|
||||||
);
|
});
|
||||||
enqueueSnackbar(
|
} catch (e) {
|
||||||
"Feedback submitted successfully!",
|
console.error(`Failed to submit feedback: ${e}`);
|
||||||
{
|
enqueueSnackbar(`Failed to submit feedback (${e})`, {
|
||||||
variant: "success",
|
variant: "error",
|
||||||
},
|
});
|
||||||
);
|
} finally {
|
||||||
} catch (e) {
|
setPending(false);
|
||||||
console.error(`Failed to submit feedback: ${e}`);
|
}
|
||||||
enqueueSnackbar(
|
onClose();
|
||||||
`Failed to submit feedback (${e})`,
|
}}
|
||||||
{
|
loading={pending}
|
||||||
variant: "error",
|
>
|
||||||
},
|
Submit
|
||||||
);
|
</LoadingButton>
|
||||||
} finally {
|
</DialogActions>
|
||||||
setPending(false);
|
</Dialog>
|
||||||
}
|
);
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
loading={pending}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</LoadingButton>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChangeEvent, useState } from 'react';
|
import { ChangeEvent, useState } from "react";
|
||||||
import {
|
import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Dialog,
|
Dialog,
|
||||||
|
@ -11,20 +11,20 @@ import {
|
||||||
Chip,
|
Chip,
|
||||||
makeStyles,
|
makeStyles,
|
||||||
Theme,
|
Theme,
|
||||||
} from '@material-ui/core';
|
} from "@material-ui/core";
|
||||||
import { Multiaddr } from 'multiaddr';
|
import { Multiaddr } from "multiaddr";
|
||||||
import { useSnackbar } from 'notistack';
|
import { useSnackbar } from "notistack";
|
||||||
import IpcInvokeButton from '../../IpcInvokeButton';
|
import IpcInvokeButton from "../../IpcInvokeButton";
|
||||||
|
|
||||||
const PRESET_RENDEZVOUS_POINTS = [
|
const PRESET_RENDEZVOUS_POINTS = [
|
||||||
'/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE',
|
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
|
||||||
'/dns4/eratosthen.es/tcp/7798/p2p/12D3KooWAh7EXXa2ZyegzLGdjvj1W4G3EXrTGrf6trraoT1MEobs',
|
"/dns4/eratosthen.es/tcp/7798/p2p/12D3KooWAh7EXXa2ZyegzLGdjvj1W4G3EXrTGrf6trraoT1MEobs",
|
||||||
];
|
];
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
chipOuter: {
|
chipOuter: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexWrap: 'wrap',
|
flexWrap: "wrap",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -39,7 +39,7 @@ export default function ListSellersDialog({
|
||||||
onClose,
|
onClose,
|
||||||
}: ListSellersDialogProps) {
|
}: ListSellersDialogProps) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [rendezvousAddress, setRendezvousAddress] = useState('');
|
const [rendezvousAddress, setRendezvousAddress] = useState("");
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
|
||||||
function handleMultiAddrChange(event: ChangeEvent<HTMLInputElement>) {
|
function handleMultiAddrChange(event: ChangeEvent<HTMLInputElement>) {
|
||||||
|
@ -49,12 +49,12 @@ export default function ListSellersDialog({
|
||||||
function getMultiAddressError(): string | null {
|
function getMultiAddressError(): string | null {
|
||||||
try {
|
try {
|
||||||
const multiAddress = new Multiaddr(rendezvousAddress);
|
const multiAddress = new Multiaddr(rendezvousAddress);
|
||||||
if (!multiAddress.protoNames().includes('p2p')) {
|
if (!multiAddress.protoNames().includes("p2p")) {
|
||||||
return 'The multi address must contain the peer id (/p2p/)';
|
return "The multi address must contain the peer id (/p2p/)";
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 'Not a valid multi address';
|
return "Not a valid multi address";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ export default function ListSellersDialog({
|
||||||
}
|
}
|
||||||
|
|
||||||
enqueueSnackbar(message, {
|
enqueueSnackbar(message, {
|
||||||
variant: 'success',
|
variant: "success",
|
||||||
autoHideDuration: 5000,
|
autoHideDuration: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ export default function ListSellersDialog({
|
||||||
label="Rendezvous point"
|
label="Rendezvous point"
|
||||||
fullWidth
|
fullWidth
|
||||||
helperText={
|
helperText={
|
||||||
getMultiAddressError() || 'Multiaddress of the rendezvous point'
|
getMultiAddressError() || "Multiaddress of the rendezvous point"
|
||||||
}
|
}
|
||||||
value={rendezvousAddress}
|
value={rendezvousAddress}
|
||||||
onChange={handleMultiAddrChange}
|
onChange={handleMultiAddrChange}
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
import { makeStyles, Box, Typography, Chip, Tooltip } from '@material-ui/core';
|
import { makeStyles, Box, Typography, Chip, Tooltip } from "@material-ui/core";
|
||||||
import { VerifiedUser } from '@material-ui/icons';
|
import { VerifiedUser } from "@material-ui/icons";
|
||||||
import { satsToBtc, secondsToDays } from 'utils/conversionUtils';
|
import { satsToBtc, secondsToDays } from "utils/conversionUtils";
|
||||||
import { ExtendedProviderStatus } from 'models/apiModel';
|
import { ExtendedProviderStatus } from "models/apiModel";
|
||||||
import {
|
import {
|
||||||
MoneroBitcoinExchangeRate,
|
MoneroBitcoinExchangeRate,
|
||||||
SatsAmount,
|
SatsAmount,
|
||||||
} from 'renderer/components/other/Units';
|
} from "renderer/components/other/Units";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
content: {
|
content: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
'& *': {
|
"& *": {
|
||||||
lineBreak: 'anywhere',
|
lineBreak: "anywhere",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chipsOuter: {
|
chipsOuter: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
marginTop: theme.spacing(1),
|
marginTop: theme.spacing(1),
|
||||||
gap: theme.spacing(0.5),
|
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)}
|
{provider.peerId.substring(0, 8)}...{provider.peerId.slice(-8)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
Exchange rate:{' '}
|
Exchange rate:{" "}
|
||||||
<MoneroBitcoinExchangeRate rate={satsToBtc(provider.price)} />
|
<MoneroBitcoinExchangeRate rate={satsToBtc(provider.price)} />
|
||||||
<br />
|
<br />
|
||||||
Minimum swap amount: <SatsAmount amount={provider.minSwapAmount} />
|
Minimum swap amount: <SatsAmount amount={provider.minSwapAmount} />
|
||||||
|
@ -49,7 +49,7 @@ export default function ProviderInfo({
|
||||||
Maximum swap amount: <SatsAmount amount={provider.maxSwapAmount} />
|
Maximum swap amount: <SatsAmount amount={provider.maxSwapAmount} />
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box className={classes.chipsOuter}>
|
<Box className={classes.chipsOuter}>
|
||||||
<Chip label={provider.testnet ? 'Testnet' : 'Mainnet'} />
|
<Chip label={provider.testnet ? "Testnet" : "Mainnet"} />
|
||||||
{provider.uptime && (
|
{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.">
|
<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`} />
|
<Chip label={`${Math.round(provider.uptime * 100)} % uptime`} />
|
||||||
|
@ -58,7 +58,7 @@ export default function ProviderInfo({
|
||||||
{provider.age ? (
|
{provider.age ? (
|
||||||
<Chip
|
<Chip
|
||||||
label={`Went online ${Math.round(secondsToDays(provider.age))} ${
|
label={`Went online ${Math.round(secondsToDays(provider.age))} ${
|
||||||
provider.age === 1 ? 'day' : 'days'
|
provider.age === 1 ? "day" : "days"
|
||||||
} ago`}
|
} ago`}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -11,21 +11,21 @@ import {
|
||||||
DialogContent,
|
DialogContent,
|
||||||
makeStyles,
|
makeStyles,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
} from '@material-ui/core';
|
} from "@material-ui/core";
|
||||||
import AddIcon from '@material-ui/icons/Add';
|
import AddIcon from "@material-ui/icons/Add";
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import SearchIcon from '@material-ui/icons/Search';
|
import SearchIcon from "@material-ui/icons/Search";
|
||||||
import { ExtendedProviderStatus } from 'models/apiModel';
|
import { ExtendedProviderStatus } from "models/apiModel";
|
||||||
import {
|
import {
|
||||||
useAllProviders,
|
useAllProviders,
|
||||||
useAppDispatch,
|
useAppDispatch,
|
||||||
useIsRpcEndpointBusy,
|
useIsRpcEndpointBusy,
|
||||||
} from 'store/hooks';
|
} from "store/hooks";
|
||||||
import { setSelectedProvider } from 'store/features/providersSlice';
|
import { setSelectedProvider } from "store/features/providersSlice";
|
||||||
import { RpcMethod } from 'models/rpcModel';
|
import { RpcMethod } from "models/rpcModel";
|
||||||
import ProviderSubmitDialog from './ProviderSubmitDialog';
|
import ProviderSubmitDialog from "./ProviderSubmitDialog";
|
||||||
import ListSellersDialog from '../listSellers/ListSellersDialog';
|
import ListSellersDialog from "../listSellers/ListSellersDialog";
|
||||||
import ProviderInfo from './ProviderInfo';
|
import ProviderInfo from "./ProviderInfo";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
dialogContent: {
|
dialogContent: {
|
||||||
|
|
|
@ -4,25 +4,25 @@ import {
|
||||||
CardContent,
|
CardContent,
|
||||||
Box,
|
Box,
|
||||||
IconButton,
|
IconButton,
|
||||||
} from '@material-ui/core';
|
} from "@material-ui/core";
|
||||||
import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
|
import ArrowForwardIosIcon from "@material-ui/icons/ArrowForwardIos";
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import ProviderInfo from './ProviderInfo';
|
import ProviderInfo from "./ProviderInfo";
|
||||||
import ProviderListDialog from './ProviderListDialog';
|
import ProviderListDialog from "./ProviderListDialog";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
inner: {
|
inner: {
|
||||||
textAlign: 'left',
|
textAlign: "left",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
},
|
},
|
||||||
providerCard: {
|
providerCard: {
|
||||||
width: '100%',
|
width: "100%",
|
||||||
},
|
},
|
||||||
providerCardContent: {
|
providerCardContent: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChangeEvent, useState } from 'react';
|
import { ChangeEvent, useState } from "react";
|
||||||
import {
|
import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Dialog,
|
Dialog,
|
||||||
|
@ -7,8 +7,8 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
Button,
|
Button,
|
||||||
} from '@material-ui/core';
|
} from "@material-ui/core";
|
||||||
import { Multiaddr } from 'multiaddr';
|
import { Multiaddr } from "multiaddr";
|
||||||
|
|
||||||
type ProviderSubmitDialogProps = {
|
type ProviderSubmitDialogProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
@ -19,23 +19,23 @@ export default function ProviderSubmitDialog({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
}: ProviderSubmitDialogProps) {
|
}: ProviderSubmitDialogProps) {
|
||||||
const [multiAddr, setMultiAddr] = useState('');
|
const [multiAddr, setMultiAddr] = useState("");
|
||||||
const [peerId, setPeerId] = useState('');
|
const [peerId, setPeerId] = useState("");
|
||||||
|
|
||||||
async function handleProviderSubmit() {
|
async function handleProviderSubmit() {
|
||||||
if (multiAddr && peerId) {
|
if (multiAddr && peerId) {
|
||||||
await fetch('https://api.unstoppableswap.net/api/submit-provider', {
|
await fetch("https://api.unstoppableswap.net/api/submit-provider", {
|
||||||
method: 'post',
|
method: "post",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
multiAddr,
|
multiAddr,
|
||||||
peerId,
|
peerId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
setMultiAddr('');
|
setMultiAddr("");
|
||||||
setPeerId('');
|
setPeerId("");
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,15 +51,15 @@ export default function ProviderSubmitDialog({
|
||||||
function getMultiAddressError(): string | null {
|
function getMultiAddressError(): string | null {
|
||||||
try {
|
try {
|
||||||
const multiAddress = new Multiaddr(multiAddr);
|
const multiAddress = new Multiaddr(multiAddr);
|
||||||
if (multiAddress.protoNames().includes('p2p')) {
|
if (multiAddress.protoNames().includes("p2p")) {
|
||||||
return 'The multi address should not contain the peer id (/p2p/)';
|
return "The multi address should not contain the peer id (/p2p/)";
|
||||||
}
|
}
|
||||||
if (multiAddress.protoNames().find((name) => name.includes('onion'))) {
|
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 "It is currently not possible to add a provider that is only reachable via Tor";
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 'Not a valid multi address';
|
return "Not a valid multi address";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ export default function ProviderSubmitDialog({
|
||||||
fullWidth
|
fullWidth
|
||||||
helperText={
|
helperText={
|
||||||
getMultiAddressError() ||
|
getMultiAddressError() ||
|
||||||
'Tells the swap client where the provider can be reached'
|
"Tells the swap client where the provider can be reached"
|
||||||
}
|
}
|
||||||
value={multiAddr}
|
value={multiAddr}
|
||||||
onChange={handleMultiAddrChange}
|
onChange={handleMultiAddrChange}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import QRCode from 'react-qr-code';
|
import QRCode from "react-qr-code";
|
||||||
import { Box } from '@material-ui/core';
|
import { Box } from "@material-ui/core";
|
||||||
|
|
||||||
export default function BitcoinQrCode({ address }: { address: string }) {
|
export default function BitcoinQrCode({ address }: { address: string }) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
height: '100%',
|
height: "100%",
|
||||||
margin: '0 auto',
|
margin: "0 auto",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<QRCode
|
<QRCode
|
||||||
value={`bitcoin:${address}`}
|
value={`bitcoin:${address}`}
|
||||||
size={256}
|
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 */
|
/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
viewBox="0 0 256 256"
|
viewBox="0 0 256 256"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { isTestnet } from 'store/config';
|
import { isTestnet } from "store/config";
|
||||||
import { getBitcoinTxExplorerUrl } from 'utils/conversionUtils';
|
import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
|
||||||
import BitcoinIcon from 'renderer/components/icons/BitcoinIcon';
|
import BitcoinIcon from "renderer/components/icons/BitcoinIcon";
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from "react";
|
||||||
import TransactionInfoBox from './TransactionInfoBox';
|
import TransactionInfoBox from "./TransactionInfoBox";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -3,8 +3,8 @@ import {
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
makeStyles,
|
makeStyles,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@material-ui/core';
|
} from "@material-ui/core";
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
subtitle: {
|
subtitle: {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { Button } from '@material-ui/core';
|
import { Button } from "@material-ui/core";
|
||||||
import { ButtonProps } from '@material-ui/core/Button/Button';
|
import { ButtonProps } from "@material-ui/core/Button/Button";
|
||||||
|
|
||||||
export default function ClipboardIconButton({
|
export default function ClipboardIconButton({
|
||||||
text,
|
text,
|
||||||
...props
|
...props
|
||||||
}: { text: string } & ButtonProps) {
|
}: { text: string } & ButtonProps) {
|
||||||
function writeToClipboard() {
|
function writeToClipboard() {
|
||||||
throw new Error('Not implemented');
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from "react";
|
||||||
import { Box, Typography } from '@material-ui/core';
|
import { Box, Typography } from "@material-ui/core";
|
||||||
import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined';
|
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
|
||||||
import InfoBox from './InfoBox';
|
import InfoBox from "./InfoBox";
|
||||||
import ClipboardIconButton from './ClipbiardIconButton';
|
import ClipboardIconButton from "./ClipbiardIconButton";
|
||||||
import BitcoinQrCode from './BitcoinQrCode';
|
import BitcoinQrCode from "./BitcoinQrCode";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -34,10 +34,10 @@ export default function DepositAddressInfoBox({
|
||||||
/>
|
/>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
gap: '0.5rem',
|
gap: "0.5rem",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box>{additionalContent}</Box>
|
<Box>{additionalContent}</Box>
|
||||||
|
|
|
@ -4,8 +4,8 @@ import {
|
||||||
makeStyles,
|
makeStyles,
|
||||||
Paper,
|
Paper,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@material-ui/core';
|
} from "@material-ui/core";
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
|
@ -18,14 +18,14 @@ type Props = {
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
padding: theme.spacing(1.5),
|
padding: theme.spacing(1.5),
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
upperContent: {
|
upperContent: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
gap: theme.spacing(0.5),
|
gap: theme.spacing(0.5),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { isTestnet } from 'store/config';
|
import { isTestnet } from "store/config";
|
||||||
import { getMoneroTxExplorerUrl } from 'utils/conversionUtils';
|
import { getMoneroTxExplorerUrl } from "utils/conversionUtils";
|
||||||
import MoneroIcon from 'renderer/components/icons/MoneroIcon';
|
import MoneroIcon from "renderer/components/icons/MoneroIcon";
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from "react";
|
||||||
import TransactionInfoBox from './TransactionInfoBox';
|
import TransactionInfoBox from "./TransactionInfoBox";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
makeStyles,
|
makeStyles,
|
||||||
} from '@material-ui/core';
|
} from "@material-ui/core";
|
||||||
import { useAppDispatch, useAppSelector } from 'store/hooks';
|
import { useAppDispatch, useAppSelector } from "store/hooks";
|
||||||
import { swapReset } from 'store/features/swapSlice';
|
import { swapReset } from "store/features/swapSlice";
|
||||||
import SwapStatePage from './pages/SwapStatePage';
|
import SwapStatePage from "./pages/SwapStatePage";
|
||||||
import SwapStateStepper from './SwapStateStepper';
|
import SwapStateStepper from "./SwapStateStepper";
|
||||||
import SwapSuspendAlert from '../SwapSuspendAlert';
|
import SwapSuspendAlert from "../SwapSuspendAlert";
|
||||||
import SwapDialogTitle from './SwapDialogTitle';
|
import SwapDialogTitle from "./SwapDialogTitle";
|
||||||
import DebugPage from './pages/DebugPage';
|
import DebugPage from "./pages/DebugPage";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
content: {
|
content: {
|
||||||
minHeight: '25rem',
|
minHeight: "25rem",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
import {
|
import { Box, DialogTitle, makeStyles, Typography } from "@material-ui/core";
|
||||||
Box,
|
import TorStatusBadge from "./pages/TorStatusBadge";
|
||||||
DialogTitle,
|
import FeedbackSubmitBadge from "./pages/FeedbackSubmitBadge";
|
||||||
makeStyles,
|
import DebugPageSwitchBadge from "./pages/DebugPageSwitchBadge";
|
||||||
Typography,
|
|
||||||
} from '@material-ui/core';
|
|
||||||
import TorStatusBadge from './pages/TorStatusBadge';
|
|
||||||
import FeedbackSubmitBadge from './pages/FeedbackSubmitBadge';
|
|
||||||
import DebugPageSwitchBadge from './pages/DebugPageSwitchBadge';
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
rightSide: {
|
rightSide: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
gridGap: theme.spacing(1),
|
gridGap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -5,165 +5,162 @@ import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
||||||
import { exhaustiveGuard } from "utils/typescriptUtils";
|
import { exhaustiveGuard } from "utils/typescriptUtils";
|
||||||
|
|
||||||
export enum PathType {
|
export enum PathType {
|
||||||
HAPPY_PATH = "happy path",
|
HAPPY_PATH = "happy path",
|
||||||
UNHAPPY_PATH = "unhappy path",
|
UNHAPPY_PATH = "unhappy path",
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActiveStep(
|
function getActiveStep(
|
||||||
stateName: SwapStateName | null,
|
stateName: SwapStateName | null,
|
||||||
processExited: boolean,
|
processExited: boolean,
|
||||||
): [PathType, number, boolean] {
|
): [PathType, number, boolean] {
|
||||||
switch (stateName) {
|
switch (stateName) {
|
||||||
/// // Happy Path
|
/// // Happy Path
|
||||||
// Step: 0 (Waiting for Bitcoin lock tx to be published)
|
// Step: 0 (Waiting for Bitcoin lock tx to be published)
|
||||||
case null:
|
case null:
|
||||||
return [PathType.HAPPY_PATH, 0, false];
|
return [PathType.HAPPY_PATH, 0, false];
|
||||||
case SwapStateName.Started:
|
case SwapStateName.Started:
|
||||||
case SwapStateName.SwapSetupCompleted:
|
case SwapStateName.SwapSetupCompleted:
|
||||||
return [PathType.HAPPY_PATH, 0, processExited];
|
return [PathType.HAPPY_PATH, 0, processExited];
|
||||||
|
|
||||||
// Step: 1 (Waiting for Bitcoin Lock confirmation and XMR Lock Publication)
|
// 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
|
// We have locked the Bitcoin and are waiting for the other party to lock their XMR
|
||||||
case SwapStateName.BtcLocked:
|
case SwapStateName.BtcLocked:
|
||||||
return [PathType.HAPPY_PATH, 1, processExited];
|
return [PathType.HAPPY_PATH, 1, processExited];
|
||||||
|
|
||||||
// Step: 2 (Waiting for XMR Lock confirmation)
|
// Step: 2 (Waiting for XMR Lock confirmation)
|
||||||
// We have locked the Bitcoin and the other party has locked their XMR
|
// We have locked the Bitcoin and the other party has locked their XMR
|
||||||
case SwapStateName.XmrLockProofReceived:
|
case SwapStateName.XmrLockProofReceived:
|
||||||
return [PathType.HAPPY_PATH, 1, processExited];
|
return [PathType.HAPPY_PATH, 1, processExited];
|
||||||
|
|
||||||
// Step: 3 (Sending Encrypted Signature and waiting for Bitcoin Redemption)
|
// Step: 3 (Sending Encrypted Signature and waiting for Bitcoin Redemption)
|
||||||
// The XMR lock transaction has been confirmed
|
// 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
|
// 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.XmrLocked:
|
||||||
case SwapStateName.EncSigSent:
|
case SwapStateName.EncSigSent:
|
||||||
return [PathType.HAPPY_PATH, 2, processExited];
|
return [PathType.HAPPY_PATH, 2, processExited];
|
||||||
|
|
||||||
// Step: 4 (Waiting for XMR Redemption)
|
// Step: 4 (Waiting for XMR Redemption)
|
||||||
case SwapStateName.BtcRedeemed:
|
case SwapStateName.BtcRedeemed:
|
||||||
return [PathType.HAPPY_PATH, 3, processExited];
|
return [PathType.HAPPY_PATH, 3, processExited];
|
||||||
|
|
||||||
// Step: 4 (Completed) (Swap completed, XMR redeemed)
|
// Step: 4 (Completed) (Swap completed, XMR redeemed)
|
||||||
case SwapStateName.XmrRedeemed:
|
case SwapStateName.XmrRedeemed:
|
||||||
return [PathType.HAPPY_PATH, 4, false];
|
return [PathType.HAPPY_PATH, 4, false];
|
||||||
|
|
||||||
// Edge Case of Happy Path where the swap is safely aborted. We "fail" at the first step.
|
// Edge Case of Happy Path where the swap is safely aborted. We "fail" at the first step.
|
||||||
case SwapStateName.SafelyAborted:
|
case SwapStateName.SafelyAborted:
|
||||||
return [PathType.HAPPY_PATH, 0, true];
|
return [PathType.HAPPY_PATH, 0, true];
|
||||||
|
|
||||||
// // Unhappy Path
|
// // Unhappy Path
|
||||||
// Step: 1 (Cancelling swap, checking if cancel transaction has been published already by the other party)
|
// Step: 1 (Cancelling swap, checking if cancel transaction has been published already by the other party)
|
||||||
case SwapStateName.CancelTimelockExpired:
|
case SwapStateName.CancelTimelockExpired:
|
||||||
return [PathType.UNHAPPY_PATH, 0, processExited];
|
return [PathType.UNHAPPY_PATH, 0, processExited];
|
||||||
|
|
||||||
// Step: 2 (Attempt to publish the Bitcoin refund transaction)
|
// Step: 2 (Attempt to publish the Bitcoin refund transaction)
|
||||||
case SwapStateName.BtcCancelled:
|
case SwapStateName.BtcCancelled:
|
||||||
return [PathType.UNHAPPY_PATH, 1, processExited];
|
return [PathType.UNHAPPY_PATH, 1, processExited];
|
||||||
|
|
||||||
// Step: 2 (Completed) (Bitcoin refunded)
|
// Step: 2 (Completed) (Bitcoin refunded)
|
||||||
case SwapStateName.BtcRefunded:
|
case SwapStateName.BtcRefunded:
|
||||||
return [PathType.UNHAPPY_PATH, 2, false];
|
return [PathType.UNHAPPY_PATH, 2, false];
|
||||||
|
|
||||||
// Step: 2 (We failed to publish the Bitcoin refund transaction)
|
// Step: 2 (We failed to publish the Bitcoin refund transaction)
|
||||||
// We failed to publish the Bitcoin refund transaction because the timelock has expired.
|
// We failed to publish the Bitcoin refund transaction because the timelock has expired.
|
||||||
// We will be punished. Nothing we can do about it now.
|
// We will be punished. Nothing we can do about it now.
|
||||||
case SwapStateName.BtcPunished:
|
case SwapStateName.BtcPunished:
|
||||||
return [PathType.UNHAPPY_PATH, 1, true];
|
return [PathType.UNHAPPY_PATH, 1, true];
|
||||||
default:
|
default:
|
||||||
return exhaustiveGuard(stateName);
|
return exhaustiveGuard(stateName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function HappyPathStepper({
|
function HappyPathStepper({
|
||||||
activeStep,
|
activeStep,
|
||||||
error,
|
error,
|
||||||
}: {
|
}: {
|
||||||
activeStep: number;
|
activeStep: number;
|
||||||
error: boolean;
|
error: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Stepper activeStep={activeStep}>
|
<Stepper activeStep={activeStep}>
|
||||||
<Step key={0}>
|
<Step key={0}>
|
||||||
<StepLabel
|
<StepLabel
|
||||||
optional={<Typography variant="caption">~12min</Typography>}
|
optional={<Typography variant="caption">~12min</Typography>}
|
||||||
error={error && activeStep === 0}
|
error={error && activeStep === 0}
|
||||||
>
|
>
|
||||||
Locking your BTC
|
Locking your BTC
|
||||||
</StepLabel>
|
</StepLabel>
|
||||||
</Step>
|
</Step>
|
||||||
<Step key={1}>
|
<Step key={1}>
|
||||||
<StepLabel
|
<StepLabel
|
||||||
optional={<Typography variant="caption">~18min</Typography>}
|
optional={<Typography variant="caption">~18min</Typography>}
|
||||||
error={error && activeStep === 1}
|
error={error && activeStep === 1}
|
||||||
>
|
>
|
||||||
They lock their XMR
|
They lock their XMR
|
||||||
</StepLabel>
|
</StepLabel>
|
||||||
</Step>
|
</Step>
|
||||||
<Step key={2}>
|
<Step key={2}>
|
||||||
<StepLabel
|
<StepLabel
|
||||||
optional={<Typography variant="caption">~2min</Typography>}
|
optional={<Typography variant="caption">~2min</Typography>}
|
||||||
error={error && activeStep === 2}
|
error={error && activeStep === 2}
|
||||||
>
|
>
|
||||||
They redeem the BTC
|
They redeem the BTC
|
||||||
</StepLabel>
|
</StepLabel>
|
||||||
</Step>
|
</Step>
|
||||||
<Step key={3}>
|
<Step key={3}>
|
||||||
<StepLabel
|
<StepLabel
|
||||||
optional={<Typography variant="caption">~2min</Typography>}
|
optional={<Typography variant="caption">~2min</Typography>}
|
||||||
error={error && activeStep === 3}
|
error={error && activeStep === 3}
|
||||||
>
|
>
|
||||||
Redeeming your XMR
|
Redeeming your XMR
|
||||||
</StepLabel>
|
</StepLabel>
|
||||||
</Step>
|
</Step>
|
||||||
</Stepper>
|
</Stepper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function UnhappyPathStepper({
|
function UnhappyPathStepper({
|
||||||
activeStep,
|
activeStep,
|
||||||
error,
|
error,
|
||||||
}: {
|
}: {
|
||||||
activeStep: number;
|
activeStep: number;
|
||||||
error: boolean;
|
error: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Stepper activeStep={activeStep}>
|
<Stepper activeStep={activeStep}>
|
||||||
<Step key={0}>
|
<Step key={0}>
|
||||||
<StepLabel
|
<StepLabel
|
||||||
optional={<Typography variant="caption">~20min</Typography>}
|
optional={<Typography variant="caption">~20min</Typography>}
|
||||||
error={error && activeStep === 0}
|
error={error && activeStep === 0}
|
||||||
>
|
>
|
||||||
Cancelling swap
|
Cancelling swap
|
||||||
</StepLabel>
|
</StepLabel>
|
||||||
</Step>
|
</Step>
|
||||||
<Step key={1}>
|
<Step key={1}>
|
||||||
<StepLabel
|
<StepLabel
|
||||||
optional={<Typography variant="caption">~20min</Typography>}
|
optional={<Typography variant="caption">~20min</Typography>}
|
||||||
error={error && activeStep === 1}
|
error={error && activeStep === 1}
|
||||||
>
|
>
|
||||||
Refunding your BTC
|
Refunding your BTC
|
||||||
</StepLabel>
|
</StepLabel>
|
||||||
</Step>
|
</Step>
|
||||||
</Stepper>
|
</Stepper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwapStateStepper() {
|
export default function SwapStateStepper() {
|
||||||
const currentSwapSpawnType = useAppSelector((s) => s.swap.spawnType);
|
const currentSwapSpawnType = useAppSelector((s) => s.swap.spawnType);
|
||||||
const stateName = useActiveSwapInfo()?.state_name ?? null;
|
const stateName = useActiveSwapInfo()?.state_name ?? null;
|
||||||
const processExited = useAppSelector((s) => !s.swap.processRunning);
|
const processExited = useAppSelector((s) => !s.swap.processRunning);
|
||||||
const [pathType, activeStep, error] = getActiveStep(
|
const [pathType, activeStep, error] = getActiveStep(stateName, processExited);
|
||||||
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 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) {
|
if (currentSwapSpawnType === SwapSpawnType.CANCEL_REFUND) {
|
||||||
return <UnhappyPathStepper activeStep={0} error={error} />;
|
return <UnhappyPathStepper activeStep={0} error={error} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pathType === PathType.HAPPY_PATH) {
|
if (pathType === PathType.HAPPY_PATH) {
|
||||||
return <HappyPathStepper activeStep={activeStep} error={error} />;
|
return <HappyPathStepper activeStep={activeStep} error={error} />;
|
||||||
}
|
}
|
||||||
return <UnhappyPathStepper activeStep={activeStep} error={error} />;
|
return <UnhappyPathStepper activeStep={activeStep} error={error} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Link, Typography } from '@material-ui/core';
|
import { Link, Typography } from "@material-ui/core";
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from "react";
|
||||||
import InfoBox from './InfoBox';
|
import InfoBox from "./InfoBox";
|
||||||
|
|
||||||
type TransactionInfoBoxProps = {
|
type TransactionInfoBoxProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Tooltip } from '@material-ui/core';
|
import { Tooltip } from "@material-ui/core";
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from "@material-ui/core/IconButton";
|
||||||
import DeveloperBoardIcon from '@material-ui/icons/DeveloperBoard';
|
import DeveloperBoardIcon from "@material-ui/icons/DeveloperBoard";
|
||||||
|
|
||||||
export default function DebugPageSwitchBadge({
|
export default function DebugPageSwitchBadge({
|
||||||
enabled,
|
enabled,
|
||||||
|
@ -14,10 +14,10 @@ export default function DebugPageSwitchBadge({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={enabled ? 'Hide debug view' : 'Show debug view'}>
|
<Tooltip title={enabled ? "Hide debug view" : "Show debug view"}>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
color={enabled ? 'primary' : 'default'}
|
color={enabled ? "primary" : "default"}
|
||||||
>
|
>
|
||||||
<DeveloperBoardIcon />
|
<DeveloperBoardIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { IconButton } from '@material-ui/core';
|
import { IconButton } from "@material-ui/core";
|
||||||
import FeedbackIcon from '@material-ui/icons/Feedback';
|
import FeedbackIcon from "@material-ui/icons/Feedback";
|
||||||
import FeedbackDialog from '../../feedback/FeedbackDialog';
|
import FeedbackDialog from "../../feedback/FeedbackDialog";
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
|
|
||||||
export default function FeedbackSubmitBadge() {
|
export default function FeedbackSubmitBadge() {
|
||||||
const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
|
const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Box } from '@material-ui/core';
|
import { Box } from "@material-ui/core";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import {
|
import {
|
||||||
isSwapStateBtcCancelled,
|
isSwapStateBtcCancelled,
|
||||||
isSwapStateBtcLockInMempool,
|
isSwapStateBtcLockInMempool,
|
||||||
|
@ -15,23 +15,23 @@ import {
|
||||||
isSwapStateXmrLockInMempool,
|
isSwapStateXmrLockInMempool,
|
||||||
isSwapStateXmrRedeemInMempool,
|
isSwapStateXmrRedeemInMempool,
|
||||||
SwapState,
|
SwapState,
|
||||||
} from '../../../../../models/storeModel';
|
} from "../../../../../models/storeModel";
|
||||||
import InitiatedPage from './init/InitiatedPage';
|
import InitiatedPage from "./init/InitiatedPage";
|
||||||
import WaitingForBitcoinDepositPage from './init/WaitingForBitcoinDepositPage';
|
import WaitingForBitcoinDepositPage from "./init/WaitingForBitcoinDepositPage";
|
||||||
import StartedPage from './in_progress/StartedPage';
|
import StartedPage from "./in_progress/StartedPage";
|
||||||
import BitcoinLockTxInMempoolPage from './in_progress/BitcoinLockTxInMempoolPage';
|
import BitcoinLockTxInMempoolPage from "./in_progress/BitcoinLockTxInMempoolPage";
|
||||||
import XmrLockTxInMempoolPage from './in_progress/XmrLockInMempoolPage';
|
import XmrLockTxInMempoolPage from "./in_progress/XmrLockInMempoolPage";
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import ProcessExitedPage from './exited/ProcessExitedPage';
|
import ProcessExitedPage from "./exited/ProcessExitedPage";
|
||||||
import XmrRedeemInMempoolPage from './done/XmrRedeemInMempoolPage';
|
import XmrRedeemInMempoolPage from "./done/XmrRedeemInMempoolPage";
|
||||||
import ReceivedQuotePage from './in_progress/ReceivedQuotePage';
|
import ReceivedQuotePage from "./in_progress/ReceivedQuotePage";
|
||||||
import BitcoinRedeemedPage from './in_progress/BitcoinRedeemedPage';
|
import BitcoinRedeemedPage from "./in_progress/BitcoinRedeemedPage";
|
||||||
import InitPage from './init/InitPage';
|
import InitPage from "./init/InitPage";
|
||||||
import XmrLockedPage from './in_progress/XmrLockedPage';
|
import XmrLockedPage from "./in_progress/XmrLockedPage";
|
||||||
import BitcoinCancelledPage from './in_progress/BitcoinCancelledPage';
|
import BitcoinCancelledPage from "./in_progress/BitcoinCancelledPage";
|
||||||
import BitcoinRefundedPage from './done/BitcoinRefundedPage';
|
import BitcoinRefundedPage from "./done/BitcoinRefundedPage";
|
||||||
import BitcoinPunishedPage from './done/BitcoinPunishedPage';
|
import BitcoinPunishedPage from "./done/BitcoinPunishedPage";
|
||||||
import { SyncingMoneroWalletPage } from './in_progress/SyncingMoneroWalletPage';
|
import { SyncingMoneroWalletPage } from "./in_progress/SyncingMoneroWalletPage";
|
||||||
|
|
||||||
export default function SwapStatePage({
|
export default function SwapStatePage({
|
||||||
swapState,
|
swapState,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { IconButton, Tooltip } from '@material-ui/core';
|
import { IconButton, Tooltip } from "@material-ui/core";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import TorIcon from '../../../icons/TorIcon';
|
import TorIcon from "../../../icons/TorIcon";
|
||||||
|
|
||||||
export default function TorStatusBadge() {
|
export default function TorStatusBadge() {
|
||||||
const tor = useAppSelector((s) => s.tor);
|
const tor = useAppSelector((s) => s.tor);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Box, DialogContentText } from '@material-ui/core';
|
import { Box, DialogContentText } from "@material-ui/core";
|
||||||
import FeedbackInfoBox from '../../../../pages/help/FeedbackInfoBox';
|
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
|
||||||
|
|
||||||
export default function BitcoinPunishedPage() {
|
export default function BitcoinPunishedPage() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,40 +5,39 @@ import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
|
||||||
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
|
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
|
||||||
|
|
||||||
export default function BitcoinRefundedPage({
|
export default function BitcoinRefundedPage({
|
||||||
state,
|
state,
|
||||||
}: {
|
}: {
|
||||||
state: SwapStateBtcRefunded | null;
|
state: SwapStateBtcRefunded | null;
|
||||||
}) {
|
}) {
|
||||||
const swap = useActiveSwapInfo();
|
const swap = useActiveSwapInfo();
|
||||||
const additionalContent = swap
|
const additionalContent = swap
|
||||||
? `Refund address: ${swap.btc_refund_address}`
|
? `Refund address: ${swap.btc_refund_address}`
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
Unfortunately, the swap was not successful. However, rest
|
Unfortunately, the swap was not successful. However, rest assured that
|
||||||
assured that all your Bitcoin has been refunded to the specified
|
all your Bitcoin has been refunded to the specified address. The swap
|
||||||
address. The swap process is now complete, and you are free to
|
process is now complete, and you are free to exit the application.
|
||||||
exit the application.
|
</DialogContentText>
|
||||||
</DialogContentText>
|
<Box
|
||||||
<Box
|
style={{
|
||||||
style={{
|
display: "flex",
|
||||||
display: "flex",
|
flexDirection: "column",
|
||||||
flexDirection: "column",
|
gap: "0.5rem",
|
||||||
gap: "0.5rem",
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{state && (
|
||||||
{state && (
|
<BitcoinTransactionInfoBox
|
||||||
<BitcoinTransactionInfoBox
|
title="Bitcoin Refund Transaction"
|
||||||
title="Bitcoin Refund Transaction"
|
txId={state.bobBtcRefundTxId}
|
||||||
txId={state.bobBtcRefundTxId}
|
loading={false}
|
||||||
loading={false}
|
additionalContent={additionalContent}
|
||||||
additionalContent={additionalContent}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
<FeedbackInfoBox />
|
||||||
<FeedbackInfoBox />
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Box, DialogContentText } from '@material-ui/core';
|
import { Box, DialogContentText } from "@material-ui/core";
|
||||||
import { SwapStateXmrRedeemInMempool } from 'models/storeModel';
|
import { SwapStateXmrRedeemInMempool } from "models/storeModel";
|
||||||
import { useActiveSwapInfo } from 'store/hooks';
|
import { useActiveSwapInfo } from "store/hooks";
|
||||||
import { getSwapXmrAmount } from 'models/rpcModel';
|
import { getSwapXmrAmount } from "models/rpcModel";
|
||||||
import MoneroTransactionInfoBox from '../../MoneroTransactionInfoBox';
|
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
|
||||||
import FeedbackInfoBox from '../../../../pages/help/FeedbackInfoBox';
|
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
|
||||||
|
|
||||||
type XmrRedeemInMempoolPageProps = {
|
type XmrRedeemInMempoolPageProps = {
|
||||||
state: SwapStateXmrRedeemInMempool | null;
|
state: SwapStateXmrRedeemInMempool | null;
|
||||||
|
@ -27,9 +27,9 @@ export default function XmrRedeemInMempoolPage({
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
gap: '0.5rem',
|
gap: "0.5rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{state && (
|
{state && (
|
||||||
|
|
|
@ -5,67 +5,67 @@ import CliLogsBox from "../../../../other/RenderedCliLog";
|
||||||
import { SwapSpawnType } from "models/cliModel";
|
import { SwapSpawnType } from "models/cliModel";
|
||||||
|
|
||||||
export default function ProcessExitedAndNotDonePage({
|
export default function ProcessExitedAndNotDonePage({
|
||||||
state,
|
state,
|
||||||
}: {
|
}: {
|
||||||
state: SwapStateProcessExited;
|
state: SwapStateProcessExited;
|
||||||
}) {
|
}) {
|
||||||
const swap = useActiveSwapInfo();
|
const swap = useActiveSwapInfo();
|
||||||
const logs = useAppSelector((s) => s.swap.logs);
|
const logs = useAppSelector((s) => s.swap.logs);
|
||||||
const spawnType = useAppSelector((s) => s.swap.spawnType);
|
const spawnType = useAppSelector((s) => s.swap.spawnType);
|
||||||
|
|
||||||
function getText() {
|
function getText() {
|
||||||
const isCancelRefund = spawnType === SwapSpawnType.CANCEL_REFUND;
|
const isCancelRefund = spawnType === SwapSpawnType.CANCEL_REFUND;
|
||||||
const hasRpcError = state.rpcError != null;
|
const hasRpcError = state.rpcError != null;
|
||||||
const hasSwap = swap != null;
|
const hasSwap = swap != null;
|
||||||
|
|
||||||
let messages = [];
|
let messages = [];
|
||||||
|
|
||||||
messages.push(
|
messages.push(
|
||||||
isCancelRefund
|
isCancelRefund
|
||||||
? "The manual cancel and refund was unsuccessful."
|
? "The manual cancel and refund was unsuccessful."
|
||||||
: "The swap exited unexpectedly without completing.",
|
: "The swap exited unexpectedly without completing.",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasSwap && !isCancelRefund) {
|
if (!hasSwap && !isCancelRefund) {
|
||||||
messages.push("No funds were locked.");
|
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(" ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
messages.push(
|
||||||
<Box>
|
hasRpcError
|
||||||
<DialogContentText>{getText()}</DialogContentText>
|
? "Check the error and the logs below for more information."
|
||||||
<Box
|
: "Check the logs below for more information.",
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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 { useActiveSwapInfo } from "store/hooks";
|
||||||
import { SwapStateName } from "models/rpcModel";
|
import { SwapStateName } from "models/rpcModel";
|
||||||
import {
|
import {
|
||||||
isSwapStateBtcPunished,
|
isSwapStateBtcPunished,
|
||||||
isSwapStateBtcRefunded,
|
isSwapStateBtcRefunded,
|
||||||
isSwapStateXmrRedeemInMempool,
|
isSwapStateXmrRedeemInMempool,
|
||||||
SwapStateProcessExited,
|
SwapStateProcessExited,
|
||||||
} from "../../../../../../models/storeModel";
|
} from "../../../../../../models/storeModel";
|
||||||
import XmrRedeemInMempoolPage from "../done/XmrRedeemInMempoolPage";
|
import XmrRedeemInMempoolPage from "../done/XmrRedeemInMempoolPage";
|
||||||
import BitcoinPunishedPage from "../done/BitcoinPunishedPage";
|
import BitcoinPunishedPage from "../done/BitcoinPunishedPage";
|
||||||
|
@ -14,34 +14,34 @@ import BitcoinRefundedPage from "../done/BitcoinRefundedPage";
|
||||||
import ProcessExitedAndNotDonePage from "./ProcessExitedAndNotDonePage";
|
import ProcessExitedAndNotDonePage from "./ProcessExitedAndNotDonePage";
|
||||||
|
|
||||||
type ProcessExitedPageProps = {
|
type ProcessExitedPageProps = {
|
||||||
state: SwapStateProcessExited;
|
state: SwapStateProcessExited;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ProcessExitedPage({ state }: ProcessExitedPageProps) {
|
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 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 (
|
if (
|
||||||
isSwapStateXmrRedeemInMempool(state.prevState) ||
|
isSwapStateXmrRedeemInMempool(state.prevState) ||
|
||||||
isSwapStateBtcRefunded(state.prevState) ||
|
isSwapStateBtcRefunded(state.prevState) ||
|
||||||
isSwapStateBtcPunished(state.prevState)
|
isSwapStateBtcPunished(state.prevState)
|
||||||
) {
|
) {
|
||||||
return <SwapStatePage swapState={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 (swap.state_name === SwapStateName.BtcRefunded) {
|
||||||
// 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
|
return <BitcoinRefundedPage state={null} />;
|
||||||
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.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
|
// 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} />;
|
return <ProcessExitedAndNotDonePage state={state} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||||
|
|
||||||
export default function BitcoinCancelledPage() {
|
export default function BitcoinCancelledPage() {
|
||||||
return <CircularProgressWithSubtitle description="Refunding your Bitcoin" />;
|
return <CircularProgressWithSubtitle description="Refunding your Bitcoin" />;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Box, DialogContentText } from '@material-ui/core';
|
import { Box, DialogContentText } from "@material-ui/core";
|
||||||
import { SwapStateBtcLockInMempool } from 'models/storeModel';
|
import { SwapStateBtcLockInMempool } from "models/storeModel";
|
||||||
import BitcoinTransactionInfoBox from '../../BitcoinTransactionInfoBox';
|
import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
|
||||||
import SwapMightBeCancelledAlert from '../../../../alert/SwapMightBeCancelledAlert';
|
import SwapMightBeCancelledAlert from "../../../../alert/SwapMightBeCancelledAlert";
|
||||||
|
|
||||||
type BitcoinLockTxInMempoolPageProps = {
|
type BitcoinLockTxInMempoolPageProps = {
|
||||||
state: SwapStateBtcLockInMempool;
|
state: SwapStateBtcLockInMempool;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||||
|
|
||||||
export default function BitcoinRedeemedPage() {
|
export default function BitcoinRedeemedPage() {
|
||||||
return <CircularProgressWithSubtitle description="Redeeming your Monero" />;
|
return <CircularProgressWithSubtitle description="Redeeming your Monero" />;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||||
|
|
||||||
export default function ReceivedQuotePage() {
|
export default function ReceivedQuotePage() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { SwapStateStarted } from 'models/storeModel';
|
import { SwapStateStarted } from "models/storeModel";
|
||||||
import { BitcoinAmount } from 'renderer/components/other/Units';
|
import { BitcoinAmount } from "renderer/components/other/Units";
|
||||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||||
|
|
||||||
export default function StartedPage({ state }: { state: SwapStateStarted }) {
|
export default function StartedPage({ state }: { state: SwapStateStarted }) {
|
||||||
const description = state.txLockDetails ? (
|
const description = state.txLockDetails ? (
|
||||||
|
@ -9,7 +9,7 @@ export default function StartedPage({ state }: { state: SwapStateStarted }) {
|
||||||
network fee of <BitcoinAmount amount={state.txLockDetails.fees} />
|
network fee of <BitcoinAmount amount={state.txLockDetails.fees} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'Locking Bitcoin'
|
"Locking Bitcoin"
|
||||||
);
|
);
|
||||||
|
|
||||||
return <CircularProgressWithSubtitle description={description} />;
|
return <CircularProgressWithSubtitle description={description} />;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||||
|
|
||||||
export function SyncingMoneroWalletPage() {
|
export function SyncingMoneroWalletPage() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Box, DialogContentText } from '@material-ui/core';
|
import { Box, DialogContentText } from "@material-ui/core";
|
||||||
import { SwapStateXmrLockInMempool } from 'models/storeModel';
|
import { SwapStateXmrLockInMempool } from "models/storeModel";
|
||||||
import MoneroTransactionInfoBox from '../../MoneroTransactionInfoBox';
|
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
|
||||||
|
|
||||||
type XmrLockTxInMempoolPageProps = {
|
type XmrLockTxInMempoolPageProps = {
|
||||||
state: SwapStateXmrLockInMempool;
|
state: SwapStateXmrLockInMempool;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||||
|
|
||||||
export default function XmrLockedPage() {
|
export default function XmrLockedPage() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import { Box, makeStyles, TextField, Typography } from '@material-ui/core';
|
import { Box, makeStyles, TextField, Typography } from "@material-ui/core";
|
||||||
import { SwapStateWaitingForBtcDeposit } from 'models/storeModel';
|
import { SwapStateWaitingForBtcDeposit } from "models/storeModel";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import { satsToBtc } from 'utils/conversionUtils';
|
import { satsToBtc } from "utils/conversionUtils";
|
||||||
import { MoneroAmount } from '../../../../other/Units';
|
import { MoneroAmount } from "../../../../other/Units";
|
||||||
|
|
||||||
const MONERO_FEE = 0.000016;
|
const MONERO_FEE = 0.000016;
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
textField: {
|
textField: {
|
||||||
'& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button': {
|
"& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button": {
|
||||||
display: 'none',
|
display: "none",
|
||||||
},
|
},
|
||||||
'& input[type=number]': {
|
"& input[type=number]": {
|
||||||
MozAppearance: 'textfield',
|
MozAppearance: "textfield",
|
||||||
},
|
},
|
||||||
maxWidth: theme.spacing(16),
|
maxWidth: theme.spacing(16),
|
||||||
},
|
},
|
||||||
|
@ -83,7 +83,7 @@ export default function DepositAmountHelper({
|
||||||
className={classes.textField}
|
className={classes.textField}
|
||||||
/>
|
/>
|
||||||
<Typography variant="subtitle2">
|
<Typography variant="subtitle2">
|
||||||
BTC will give you approximately{' '}
|
BTC will give you approximately{" "}
|
||||||
<MoneroAmount amount={calcXMRAmount()} />.
|
<MoneroAmount amount={calcXMRAmount()} />.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||||
import { MoneroWalletRpcUpdateState } from '../../../../../../models/storeModel';
|
import { MoneroWalletRpcUpdateState } from "../../../../../../models/storeModel";
|
||||||
|
|
||||||
export default function DownloadingMoneroWalletRpcPage({
|
export default function DownloadingMoneroWalletRpcPage({
|
||||||
updateState,
|
updateState,
|
||||||
|
|
|
@ -1,32 +1,28 @@
|
||||||
import { Box, DialogContentText, makeStyles } from '@material-ui/core';
|
import { Box, DialogContentText, makeStyles } from "@material-ui/core";
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import BitcoinAddressTextField from 'renderer/components/inputs/BitcoinAddressTextField';
|
import BitcoinAddressTextField from "renderer/components/inputs/BitcoinAddressTextField";
|
||||||
import MoneroAddressTextField from 'renderer/components/inputs/MoneroAddressTextField';
|
import MoneroAddressTextField from "renderer/components/inputs/MoneroAddressTextField";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
|
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
||||||
import { isTestnet } from 'store/config';
|
import { isTestnet } from "store/config";
|
||||||
import RemainingFundsWillBeUsedAlert from '../../../../alert/RemainingFundsWillBeUsedAlert';
|
import RemainingFundsWillBeUsedAlert from "../../../../alert/RemainingFundsWillBeUsedAlert";
|
||||||
import IpcInvokeButton from '../../../../IpcInvokeButton';
|
import IpcInvokeButton from "../../../../IpcInvokeButton";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
initButton: {
|
initButton: {
|
||||||
marginTop: theme.spacing(1),
|
marginTop: theme.spacing(1),
|
||||||
},
|
},
|
||||||
fieldsOuter: {
|
fieldsOuter: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function InitPage() {
|
export default function InitPage() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [redeemAddress, setRedeemAddress] = useState(
|
const [redeemAddress, setRedeemAddress] = useState("");
|
||||||
''
|
const [refundAddress, setRefundAddress] = useState("");
|
||||||
);
|
|
||||||
const [refundAddress, setRefundAddress] = useState(
|
|
||||||
''
|
|
||||||
);
|
|
||||||
const [redeemAddressValid, setRedeemAddressValid] = useState(false);
|
const [redeemAddressValid, setRedeemAddressValid] = useState(false);
|
||||||
const [refundAddressValid, setRefundAddressValid] = useState(false);
|
const [refundAddressValid, setRefundAddressValid] = useState(false);
|
||||||
const selectedProvider = useAppSelector(
|
const selectedProvider = useAppSelector(
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import { SwapSpawnType } from 'models/cliModel';
|
import { SwapSpawnType } from "models/cliModel";
|
||||||
import CircularProgressWithSubtitle from '../../CircularProgressWithSubtitle';
|
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||||
|
|
||||||
export default function InitiatedPage() {
|
export default function InitiatedPage() {
|
||||||
const description = useAppSelector((s) => {
|
const description = useAppSelector((s) => {
|
||||||
switch (s.swap.spawnType) {
|
switch (s.swap.spawnType) {
|
||||||
case SwapSpawnType.INIT:
|
case SwapSpawnType.INIT:
|
||||||
return 'Requesting quote from provider...';
|
return "Requesting quote from provider...";
|
||||||
case SwapSpawnType.RESUME:
|
case SwapSpawnType.RESUME:
|
||||||
return 'Resuming swap...';
|
return "Resuming swap...";
|
||||||
case SwapSpawnType.CANCEL_REFUND:
|
case SwapSpawnType.CANCEL_REFUND:
|
||||||
return 'Attempting to cancel & refund swap...';
|
return "Attempting to cancel & refund swap...";
|
||||||
default:
|
default:
|
||||||
// Should never be hit
|
// Should never be hit
|
||||||
return 'Initiating swap...';
|
return "Initiating swap...";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
import { Box, makeStyles, Typography } from '@material-ui/core';
|
import { Box, makeStyles, Typography } from "@material-ui/core";
|
||||||
import { SwapStateWaitingForBtcDeposit } from 'models/storeModel';
|
import { SwapStateWaitingForBtcDeposit } from "models/storeModel";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import DepositAddressInfoBox from '../../DepositAddressInfoBox';
|
import DepositAddressInfoBox from "../../DepositAddressInfoBox";
|
||||||
import BitcoinIcon from '../../../../icons/BitcoinIcon';
|
import BitcoinIcon from "../../../../icons/BitcoinIcon";
|
||||||
import DepositAmountHelper from './DepositAmountHelper';
|
import DepositAmountHelper from "./DepositAmountHelper";
|
||||||
import {
|
import {
|
||||||
BitcoinAmount,
|
BitcoinAmount,
|
||||||
MoneroBitcoinExchangeRate,
|
MoneroBitcoinExchangeRate,
|
||||||
SatsAmount,
|
SatsAmount,
|
||||||
} from '../../../../other/Units';
|
} from "../../../../other/Units";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
amountHelper: {
|
amountHelper: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
additionalContent: {
|
additionalContent: {
|
||||||
paddingTop: theme.spacing(1),
|
paddingTop: theme.spacing(1),
|
||||||
gap: theme.spacing(0.5),
|
gap: theme.spacing(0.5),
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -45,13 +45,13 @@ export default function WaitingForBtcDepositPage({
|
||||||
<ul>
|
<ul>
|
||||||
{bitcoinBalance > 0 ? (
|
{bitcoinBalance > 0 ? (
|
||||||
<li>
|
<li>
|
||||||
You have already deposited{' '}
|
You have already deposited{" "}
|
||||||
<SatsAmount amount={bitcoinBalance} />
|
<SatsAmount amount={bitcoinBalance} />
|
||||||
</li>
|
</li>
|
||||||
) : null}
|
) : null}
|
||||||
<li>
|
<li>
|
||||||
Send any amount between{' '}
|
Send any amount between{" "}
|
||||||
<BitcoinAmount amount={state.minDeposit} /> and{' '}
|
<BitcoinAmount amount={state.minDeposit} /> and{" "}
|
||||||
<BitcoinAmount amount={state.maxDeposit} /> to the address
|
<BitcoinAmount amount={state.maxDeposit} /> to the address
|
||||||
above
|
above
|
||||||
{bitcoinBalance > 0 && (
|
{bitcoinBalance > 0 && (
|
||||||
|
@ -60,11 +60,11 @@ export default function WaitingForBtcDepositPage({
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
All Bitcoin sent to this this address will converted into
|
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} />
|
<MoneroBitcoinExchangeRate rate={state.price} />
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
The network fee of{' '}
|
The network fee of{" "}
|
||||||
<BitcoinAmount amount={state.minBitcoinLockTxFee} /> will
|
<BitcoinAmount amount={state.minBitcoinLockTxFee} /> will
|
||||||
automatically be deducted from the deposited coins
|
automatically be deducted from the deposited coins
|
||||||
</li>
|
</li>
|
||||||
|
@ -74,9 +74,7 @@ export default function WaitingForBtcDepositPage({
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Typography>
|
</Typography>
|
||||||
<DepositAmountHelper
|
<DepositAmountHelper state={state} />
|
||||||
state={state}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
icon={<BitcoinIcon />}
|
icon={<BitcoinIcon />}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { useState } from "react";
|
||||||
import { withdrawBtc } from "renderer/rpc";
|
import { withdrawBtc } from "renderer/rpc";
|
||||||
import BtcTxInMempoolPageContent from "./pages/BitcoinWithdrawTxInMempoolPage";
|
import BtcTxInMempoolPageContent from "./pages/BitcoinWithdrawTxInMempoolPage";
|
||||||
import AddressInputPage from "./pages/AddressInputPage";
|
import AddressInputPage from "./pages/AddressInputPage";
|
||||||
|
import WithdrawDialogContent from "./WithdrawDialogContent";
|
||||||
|
|
||||||
export default function WithdrawDialog({
|
export default function WithdrawDialog({
|
||||||
open,
|
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 (
|
return (
|
||||||
<Dialog open onClose={onCancel} maxWidth="sm" fullWidth>
|
<Dialog open={open} onClose={onCancel} maxWidth="sm" fullWidth>
|
||||||
<DialogHeader title="Withdraw Bitcoin" />
|
<DialogHeader title="Withdraw Bitcoin" />
|
||||||
{withdrawTxId === null ? (
|
<WithdrawDialogContent isPending={pending} withdrawTxId={withdrawTxId}>
|
||||||
<AddressInputPage
|
|
||||||
setWithdrawAddress={setWithdrawAddress}
|
|
||||||
withdrawAddress={withdrawAddress}
|
|
||||||
setWithdrawAddressValid={setWithdrawAddressValid}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<BtcTxInMempoolPageContent
|
|
||||||
withdrawTxId={withdrawTxId}
|
|
||||||
onCancel={onCancel}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<DialogActions>
|
|
||||||
{withdrawTxId === null ? (
|
{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
|
<PromiseInvokeButton
|
||||||
|
displayErrorSnackbar
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={!withdrawAddressValid}
|
disabled={!withdrawAddressValid}
|
||||||
|
@ -65,10 +69,6 @@ export default function WithdrawDialog({
|
||||||
>
|
>
|
||||||
Withdraw
|
Withdraw
|
||||||
</PromiseInvokeButton>
|
</PromiseInvokeButton>
|
||||||
) : (
|
|
||||||
<Button onClick={onCancel} color="primary" disabled={pending}>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
@ -1,27 +1,31 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from "react";
|
||||||
import { Box, DialogContent, makeStyles } from '@material-ui/core';
|
import { Box, DialogContent, makeStyles } from "@material-ui/core";
|
||||||
import WithdrawStepper from './WithdrawStepper';
|
import WithdrawStepper from "./WithdrawStepper";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
outer: {
|
outer: {
|
||||||
minHeight: '15rem',
|
minHeight: "15rem",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function WithdrawDialogContent({
|
export default function WithdrawDialogContent({
|
||||||
children,
|
children,
|
||||||
|
isPending,
|
||||||
|
withdrawTxId,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
isPending: boolean;
|
||||||
|
withdrawTxId: string | null;
|
||||||
}) {
|
}) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent dividers className={classes.outer}>
|
<DialogContent dividers className={classes.outer}>
|
||||||
<Box>{children}</Box>
|
<Box>{children}</Box>
|
||||||
<WithdrawStepper />
|
<WithdrawStepper isPending={isPending} withdrawTxId={withdrawTxId} />
|
||||||
</DialogContent>
|
</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 { Step, StepLabel, Stepper } from "@material-ui/core";
|
||||||
import { useAppSelector, useIsRpcEndpointBusy } from 'store/hooks';
|
import { useAppSelector, useIsRpcEndpointBusy } from "store/hooks";
|
||||||
import { RpcMethod } from 'models/rpcModel';
|
|
||||||
|
|
||||||
function getActiveStep(
|
function getActiveStep(isPending: boolean, withdrawTxId: string | null) {
|
||||||
isWithdrawInProgress: boolean,
|
if (isPending) {
|
||||||
withdrawTxId: string | null,
|
|
||||||
) {
|
|
||||||
if (isWithdrawInProgress) {
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (withdrawTxId !== null) {
|
if (withdrawTxId !== null) {
|
||||||
|
@ -15,12 +11,15 @@ function getActiveStep(
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WithdrawStepper() {
|
export default function WithdrawStepper({
|
||||||
const isWithdrawInProgress = useIsRpcEndpointBusy(RpcMethod.WITHDRAW_BTC);
|
isPending,
|
||||||
const withdrawTxId = useAppSelector((s) => s.rpc.state.withdrawTxId);
|
withdrawTxId,
|
||||||
|
}: {
|
||||||
|
isPending: boolean;
|
||||||
|
withdrawTxId: string | null;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Stepper activeStep={getActiveStep(isWithdrawInProgress, withdrawTxId)}>
|
<Stepper activeStep={getActiveStep(isPending, withdrawTxId)}>
|
||||||
<Step key={0}>
|
<Step key={0}>
|
||||||
<StepLabel>Enter withdraw address</StepLabel>
|
<StepLabel>Enter withdraw address</StepLabel>
|
||||||
</Step>
|
</Step>
|
||||||
|
|
|
@ -15,20 +15,18 @@ export default function AddressInputPage({
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WithdrawDialogContent>
|
<DialogContentText>
|
||||||
<DialogContentText>
|
To withdraw the Bitcoin inside the internal wallet, please enter an
|
||||||
To withdraw the BTC of the internal wallet, please enter an address.
|
address. All funds will be sent to that address.
|
||||||
All funds will be sent to that address.
|
</DialogContentText>
|
||||||
</DialogContentText>
|
|
||||||
|
|
||||||
<BitcoinAddressTextField
|
<BitcoinAddressTextField
|
||||||
address={withdrawAddress}
|
address={withdrawAddress}
|
||||||
onAddressChange={setWithdrawAddress}
|
onAddressChange={setWithdrawAddress}
|
||||||
onAddressValidityChange={setWithdrawAddressValid}
|
onAddressValidityChange={setWithdrawAddressValid}
|
||||||
helperText="All Bitcoin of the internal wallet will be transferred to this address"
|
helperText="All Bitcoin of the internal wallet will be transferred to this address"
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</WithdrawDialogContent>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,18 +11,16 @@ export default function BtcTxInMempoolPageContent({
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WithdrawDialogContent>
|
<DialogContentText>
|
||||||
<DialogContentText>
|
All funds of the internal Bitcoin wallet have been transferred to your
|
||||||
All funds of the internal Bitcoin wallet have been transferred to your
|
withdraw address.
|
||||||
withdraw address.
|
</DialogContentText>
|
||||||
</DialogContentText>
|
<BitcoinTransactionInfoBox
|
||||||
<BitcoinTransactionInfoBox
|
txId={withdrawTxId}
|
||||||
txId={withdrawTxId}
|
loading={false}
|
||||||
loading={false}
|
title="Bitcoin Withdraw Transaction"
|
||||||
title="Bitcoin Withdraw Transaction"
|
additionalContent={null}
|
||||||
additionalContent={null}
|
/>
|
||||||
/>
|
|
||||||
</WithdrawDialogContent>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Drawer, makeStyles, Box } from '@material-ui/core';
|
import { Drawer, makeStyles, Box } from "@material-ui/core";
|
||||||
import NavigationHeader from './NavigationHeader';
|
import NavigationHeader from "./NavigationHeader";
|
||||||
import NavigationFooter from './NavigationFooter';
|
import NavigationFooter from "./NavigationFooter";
|
||||||
|
|
||||||
export const drawerWidth = 240;
|
export const drawerWidth = 240;
|
||||||
|
|
||||||
|
@ -13,11 +13,11 @@ const useStyles = makeStyles({
|
||||||
width: drawerWidth,
|
width: drawerWidth,
|
||||||
},
|
},
|
||||||
drawerContainer: {
|
drawerContainer: {
|
||||||
overflow: 'auto',
|
overflow: "auto",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
import RedditIcon from '@material-ui/icons/Reddit';
|
import RedditIcon from "@material-ui/icons/Reddit";
|
||||||
import GitHubIcon from '@material-ui/icons/GitHub';
|
import GitHubIcon from "@material-ui/icons/GitHub";
|
||||||
import { Box, makeStyles } from '@material-ui/core';
|
import { Box, makeStyles } from "@material-ui/core";
|
||||||
import LinkIconButton from '../icons/LinkIconButton';
|
import LinkIconButton from "../icons/LinkIconButton";
|
||||||
import UnfinishedSwapsAlert from '../alert/UnfinishedSwapsAlert';
|
import UnfinishedSwapsAlert from "../alert/UnfinishedSwapsAlert";
|
||||||
import FundsLeftInWalletAlert from '../alert/FundsLeftInWalletAlert';
|
import FundsLeftInWalletAlert from "../alert/FundsLeftInWalletAlert";
|
||||||
import RpcStatusAlert from '../alert/RpcStatusAlert';
|
import RpcStatusAlert from "../alert/RpcStatusAlert";
|
||||||
import DiscordIcon from '../icons/DiscordIcon';
|
import DiscordIcon from "../icons/DiscordIcon";
|
||||||
import { DISCORD_URL } from '../pages/help/ContactInfoBox';
|
import { DISCORD_URL } from "../pages/help/ContactInfoBox";
|
||||||
import MoneroWalletRpcUpdatingAlert from '../alert/MoneroWalletRpcUpdatingAlert';
|
import MoneroWalletRpcUpdatingAlert from "../alert/MoneroWalletRpcUpdatingAlert";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
linksOuter: {
|
linksOuter: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
justifyContent: 'space-evenly',
|
justifyContent: "space-evenly",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Box, List } from '@material-ui/core';
|
import { Box, List } from "@material-ui/core";
|
||||||
import SwapHorizOutlinedIcon from '@material-ui/icons/SwapHorizOutlined';
|
import SwapHorizOutlinedIcon from "@material-ui/icons/SwapHorizOutlined";
|
||||||
import HistoryOutlinedIcon from '@material-ui/icons/HistoryOutlined';
|
import HistoryOutlinedIcon from "@material-ui/icons/HistoryOutlined";
|
||||||
import AccountBalanceWalletIcon from '@material-ui/icons/AccountBalanceWallet';
|
import AccountBalanceWalletIcon from "@material-ui/icons/AccountBalanceWallet";
|
||||||
import HelpOutlineIcon from '@material-ui/icons/HelpOutline';
|
import HelpOutlineIcon from "@material-ui/icons/HelpOutline";
|
||||||
import RouteListItemIconButton from './RouteListItemIconButton';
|
import RouteListItemIconButton from "./RouteListItemIconButton";
|
||||||
import UnfinishedSwapsBadge from './UnfinishedSwapsCountBadge';
|
import UnfinishedSwapsBadge from "./UnfinishedSwapsCountBadge";
|
||||||
|
|
||||||
export default function NavigationHeader() {
|
export default function NavigationHeader() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from "react";
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from "react-router-dom";
|
||||||
import { ListItem, ListItemIcon, ListItemText } from '@material-ui/core';
|
import { ListItem, ListItemIcon, ListItemText } from "@material-ui/core";
|
||||||
|
|
||||||
export default function RouteListItemIconButton({
|
export default function RouteListItemIconButton({
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Badge } from '@material-ui/core';
|
import { Badge } from "@material-ui/core";
|
||||||
import { useResumeableSwapsCount } from 'store/hooks';
|
import { useResumeableSwapsCount } from "store/hooks";
|
||||||
|
|
||||||
export default function UnfinishedSwapsBadge({
|
export default function UnfinishedSwapsBadge({
|
||||||
children,
|
children,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import { Box, IconButton, TextField } from '@material-ui/core';
|
import { Box, IconButton, TextField } from "@material-ui/core";
|
||||||
import SearchIcon from '@material-ui/icons/Search';
|
import SearchIcon from "@material-ui/icons/Search";
|
||||||
import CloseIcon from '@material-ui/icons/Close';
|
import CloseIcon from "@material-ui/icons/Close";
|
||||||
|
|
||||||
export function ExpandableSearchBox({
|
export function ExpandableSearchBox({
|
||||||
query,
|
query,
|
||||||
|
@ -13,8 +13,8 @@ export function ExpandableSearchBox({
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box style={{ display: 'flex', justifyContent: 'center' }}>
|
<Box style={{ display: "flex", justifyContent: "center" }}>
|
||||||
<Box style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
<Box style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
|
||||||
{expanded ? (
|
{expanded ? (
|
||||||
<>
|
<>
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -26,7 +26,7 @@ export function ExpandableSearchBox({
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setExpanded(false);
|
setExpanded(false);
|
||||||
setQuery('');
|
setQuery("");
|
||||||
}}
|
}}
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import humanizeDuration from 'humanize-duration';
|
import humanizeDuration from "humanize-duration";
|
||||||
|
|
||||||
const AVG_BLOCK_TIME_MS = 10 * 60 * 1000;
|
const AVG_BLOCK_TIME_MS = 10 * 60 * 1000;
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ export default function HumanizedBitcoinBlockDuration({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{`${humanizeDuration(blocks * AVG_BLOCK_TIME_MS, {
|
{`${humanizeDuration(blocks * AVG_BLOCK_TIME_MS, {
|
||||||
conjunction: ' and ',
|
conjunction: " and ",
|
||||||
})} (${blocks} blocks)`}
|
})} (${blocks} blocks)`}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import TreeView from '@material-ui/lab/TreeView';
|
import TreeView from "@material-ui/lab/TreeView";
|
||||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
||||||
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
|
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
|
||||||
import TreeItem from '@material-ui/lab/TreeItem';
|
import TreeItem from "@material-ui/lab/TreeItem";
|
||||||
import ScrollablePaperTextBox from './ScrollablePaperTextBox';
|
import ScrollablePaperTextBox from "./ScrollablePaperTextBox";
|
||||||
|
|
||||||
interface JsonTreeViewProps {
|
interface JsonTreeViewProps {
|
||||||
data: any;
|
data: any;
|
||||||
|
@ -13,7 +13,7 @@ export default function JsonTreeView({ data, label }: JsonTreeViewProps) {
|
||||||
const renderTree = (nodes: any, parentId: string) => {
|
const renderTree = (nodes: any, parentId: string) => {
|
||||||
return Object.keys(nodes).map((key, _) => {
|
return Object.keys(nodes).map((key, _) => {
|
||||||
const nodeId = `${parentId}.${key}`;
|
const nodeId = `${parentId}.${key}`;
|
||||||
if (typeof nodes[key] === 'object' && nodes[key] !== null) {
|
if (typeof nodes[key] === "object" && nodes[key] !== null) {
|
||||||
return (
|
return (
|
||||||
<TreeItem nodeId={nodeId} label={key} key={nodeId}>
|
<TreeItem nodeId={nodeId} label={key} key={nodeId}>
|
||||||
{renderTree(nodes[key], nodeId)}
|
{renderTree(nodes[key], nodeId)}
|
||||||
|
@ -38,10 +38,10 @@ export default function JsonTreeView({ data, label }: JsonTreeViewProps) {
|
||||||
<TreeView
|
<TreeView
|
||||||
defaultCollapseIcon={<ExpandMoreIcon />}
|
defaultCollapseIcon={<ExpandMoreIcon />}
|
||||||
defaultExpandIcon={<ChevronRightIcon />}
|
defaultExpandIcon={<ChevronRightIcon />}
|
||||||
defaultExpanded={['root']}
|
defaultExpanded={["root"]}
|
||||||
>
|
>
|
||||||
<TreeItem nodeId="root" label={label}>
|
<TreeItem nodeId="root" label={label}>
|
||||||
{renderTree(data ?? {}, 'root')}
|
{renderTree(data ?? {}, "root")}
|
||||||
</TreeItem>
|
</TreeItem>
|
||||||
</TreeView>,
|
</TreeView>,
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import Button, { ButtonProps } from '@material-ui/core/Button';
|
import Button, { ButtonProps } from "@material-ui/core/Button";
|
||||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||||
|
|
||||||
interface LoadingButtonProps extends ButtonProps {
|
interface LoadingButtonProps extends ButtonProps {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
|
|
@ -1,47 +1,47 @@
|
||||||
import { Box, Chip, Typography } from '@material-ui/core';
|
import { Box, Chip, Typography } from "@material-ui/core";
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from "react";
|
||||||
import { CliLog } from 'models/cliModel';
|
import { CliLog } from "models/cliModel";
|
||||||
import { logsToRawString } from 'utils/parseUtils';
|
import { logsToRawString } from "utils/parseUtils";
|
||||||
import ScrollablePaperTextBox from './ScrollablePaperTextBox';
|
import ScrollablePaperTextBox from "./ScrollablePaperTextBox";
|
||||||
|
|
||||||
function RenderedCliLog({ log }: { log: CliLog }) {
|
function RenderedCliLog({ log }: { log: CliLog }) {
|
||||||
const { timestamp, level, fields } = log;
|
const { timestamp, level, fields } = log;
|
||||||
|
|
||||||
const levelColorMap = {
|
const levelColorMap = {
|
||||||
DEBUG: '#1976d2', // Blue
|
DEBUG: "#1976d2", // Blue
|
||||||
INFO: '#388e3c', // Green
|
INFO: "#388e3c", // Green
|
||||||
WARN: '#fbc02d', // Yellow
|
WARN: "#fbc02d", // Yellow
|
||||||
ERROR: '#d32f2f', // Red
|
ERROR: "#d32f2f", // Red
|
||||||
TRACE: '#8e24aa', // Purple
|
TRACE: "#8e24aa", // Purple
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: '0.3rem',
|
gap: "0.3rem",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Chip
|
<Chip
|
||||||
label={level}
|
label={level}
|
||||||
size="small"
|
size="small"
|
||||||
style={{ backgroundColor: levelColorMap[level], color: 'white' }}
|
style={{ backgroundColor: levelColorMap[level], color: "white" }}
|
||||||
/>
|
/>
|
||||||
<Chip label={timestamp} size="small" variant="outlined" />
|
<Chip label={timestamp} size="small" variant="outlined" />
|
||||||
<Typography variant="subtitle2">{fields.message}</Typography>
|
<Typography variant="subtitle2">{fields.message}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
paddingLeft: '1rem',
|
paddingLeft: "1rem",
|
||||||
paddingTop: '0.2rem',
|
paddingTop: "0.2rem",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Object.entries(fields).map(([key, value]) => {
|
{Object.entries(fields).map(([key, value]) => {
|
||||||
if (key !== 'message') {
|
if (key !== "message") {
|
||||||
return (
|
return (
|
||||||
<Typography variant="caption" key={key}>
|
<Typography variant="caption" key={key}>
|
||||||
{key}: {JSON.stringify(value)}
|
{key}: {JSON.stringify(value)}
|
||||||
|
@ -62,7 +62,7 @@ export default function CliLogsBox({
|
||||||
label: string;
|
label: string;
|
||||||
logs: (CliLog | string)[];
|
logs: (CliLog | string)[];
|
||||||
}) {
|
}) {
|
||||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||||
|
|
||||||
const memoizedLogs = useMemo(() => {
|
const memoizedLogs = useMemo(() => {
|
||||||
if (searchQuery.length === 0) {
|
if (searchQuery.length === 0) {
|
||||||
|
@ -80,7 +80,7 @@ export default function CliLogsBox({
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
setSearchQuery={setSearchQuery}
|
setSearchQuery={setSearchQuery}
|
||||||
rows={memoizedLogs.map((log) =>
|
rows={memoizedLogs.map((log) =>
|
||||||
typeof log === 'string' ? (
|
typeof log === "string" ? (
|
||||||
<Typography component="pre">{log}</Typography>
|
<Typography component="pre">{log}</Typography>
|
||||||
) : (
|
) : (
|
||||||
<RenderedCliLog log={log} key={JSON.stringify(log)} />
|
<RenderedCliLog log={log} key={JSON.stringify(log)} />
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { Box, Divider, IconButton, Paper, Typography } from '@material-ui/core';
|
import { Box, Divider, IconButton, Paper, Typography } from "@material-ui/core";
|
||||||
import { ReactNode, useRef } from 'react';
|
import { ReactNode, useRef } from "react";
|
||||||
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
|
||||||
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp';
|
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
|
||||||
import { VList, VListHandle } from 'virtua';
|
import { VList, VListHandle } from "virtua";
|
||||||
import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined';
|
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
|
||||||
import { ExpandableSearchBox } from './ExpandableSearchBox';
|
import { ExpandableSearchBox } from "./ExpandableSearchBox";
|
||||||
|
|
||||||
const MIN_HEIGHT = '10rem';
|
const MIN_HEIGHT = "10rem";
|
||||||
|
|
||||||
export default function ScrollablePaperTextBox({
|
export default function ScrollablePaperTextBox({
|
||||||
rows,
|
rows,
|
||||||
|
@ -41,31 +41,31 @@ export default function ScrollablePaperTextBox({
|
||||||
<Paper
|
<Paper
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
gap: '0.5rem',
|
gap: "0.5rem",
|
||||||
padding: '0.5rem',
|
padding: "0.5rem",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography>{title}</Typography>
|
<Typography>{title}</Typography>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
overflow: 'auto',
|
overflow: "auto",
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: "nowrap",
|
||||||
maxHeight: minHeight,
|
maxHeight: minHeight,
|
||||||
minHeight,
|
minHeight,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
gap: '0.5rem',
|
gap: "0.5rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<VList ref={virtuaEl} style={{ height: MIN_HEIGHT, width: '100%' }}>
|
<VList ref={virtuaEl} style={{ height: MIN_HEIGHT, width: "100%" }}>
|
||||||
{rows}
|
{rows}
|
||||||
</VList>
|
</VList>
|
||||||
</Box>
|
</Box>
|
||||||
<Box style={{ display: 'flex', gap: '0.5rem' }}>
|
<Box style={{ display: "flex", gap: "0.5rem" }}>
|
||||||
<IconButton onClick={onCopy} size="small">
|
<IconButton onClick={onCopy} size="small">
|
||||||
<FileCopyOutlinedIcon />
|
<FileCopyOutlinedIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import { Box, Button, makeStyles, Typography } from '@material-ui/core';
|
import { Box, Button, makeStyles, Typography } from "@material-ui/core";
|
||||||
import InfoBox from '../../modal/swap/InfoBox';
|
import InfoBox from "../../modal/swap/InfoBox";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
spacedBox: {
|
spacedBox: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const GITHUB_ISSUE_URL =
|
const GITHUB_ISSUE_URL =
|
||||||
'https://github.com/UnstoppableSwap/unstoppableswap-gui/issues/new/choose';
|
"https://github.com/UnstoppableSwap/unstoppableswap-gui/issues/new/choose";
|
||||||
const MATRIX_ROOM_URL = 'https://matrix.to/#/#unstoppableswap:matrix.org';
|
const MATRIX_ROOM_URL = "https://matrix.to/#/#unstoppableswap:matrix.org";
|
||||||
export const DISCORD_URL = 'https://discord.gg/APJ6rJmq';
|
export const DISCORD_URL = "https://discord.gg/APJ6rJmq";
|
||||||
|
|
||||||
export default function ContactInfoBox() {
|
export default function ContactInfoBox() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Typography } from '@material-ui/core';
|
import { Typography } from "@material-ui/core";
|
||||||
import DepositAddressInfoBox from '../../modal/swap/DepositAddressInfoBox';
|
import DepositAddressInfoBox from "../../modal/swap/DepositAddressInfoBox";
|
||||||
import MoneroIcon from '../../icons/MoneroIcon';
|
import MoneroIcon from "../../icons/MoneroIcon";
|
||||||
|
|
||||||
const XMR_DONATE_ADDRESS =
|
const XMR_DONATE_ADDRESS =
|
||||||
'87jS4C7ngk9EHdqFFuxGFgg8AyH63dRUoULshWDybFJaP75UA89qsutG5B1L1QTc4w228nsqsv8EjhL7bz8fB3611Mh98mg';
|
"87jS4C7ngk9EHdqFFuxGFgg8AyH63dRUoULshWDybFJaP75UA89qsutG5B1L1QTc4w228nsqsv8EjhL7bz8fB3611Mh98mg";
|
||||||
|
|
||||||
export default function DonateInfoBox() {
|
export default function DonateInfoBox() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Button, Typography } from '@material-ui/core';
|
import { Button, Typography } from "@material-ui/core";
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import InfoBox from '../../modal/swap/InfoBox';
|
import InfoBox from "../../modal/swap/InfoBox";
|
||||||
import FeedbackDialog from '../../modal/feedback/FeedbackDialog';
|
import FeedbackDialog from "../../modal/feedback/FeedbackDialog";
|
||||||
|
|
||||||
export default function FeedbackInfoBox() {
|
export default function FeedbackInfoBox() {
|
||||||
const [showDialog, setShowDialog] = useState(false);
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { Box, makeStyles } from '@material-ui/core';
|
import { Box, makeStyles } from "@material-ui/core";
|
||||||
import ContactInfoBox from './ContactInfoBox';
|
import ContactInfoBox from "./ContactInfoBox";
|
||||||
import FeedbackInfoBox from './FeedbackInfoBox';
|
import FeedbackInfoBox from "./FeedbackInfoBox";
|
||||||
import DonateInfoBox from './DonateInfoBox';
|
import DonateInfoBox from "./DonateInfoBox";
|
||||||
import TorInfoBox from './TorInfoBox';
|
import TorInfoBox from "./TorInfoBox";
|
||||||
import RpcControlBox from './RpcControlBox';
|
import RpcControlBox from "./RpcControlBox";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import { Box, makeStyles } from '@material-ui/core';
|
import { Box, makeStyles } from "@material-ui/core";
|
||||||
import IpcInvokeButton from 'renderer/components/IpcInvokeButton';
|
import IpcInvokeButton from "renderer/components/IpcInvokeButton";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import StopIcon from '@material-ui/icons/Stop';
|
import StopIcon from "@material-ui/icons/Stop";
|
||||||
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
|
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
||||||
import { RpcProcessStateType } from 'models/rpcModel';
|
import { RpcProcessStateType } from "models/rpcModel";
|
||||||
import InfoBox from '../../modal/swap/InfoBox';
|
import InfoBox from "../../modal/swap/InfoBox";
|
||||||
import CliLogsBox from '../../other/RenderedCliLog';
|
import CliLogsBox from "../../other/RenderedCliLog";
|
||||||
import FolderOpenIcon from '@material-ui/icons/FolderOpen';
|
import FolderOpenIcon from "@material-ui/icons/FolderOpen";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
actionsOuter: {
|
actionsOuter: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { Box, makeStyles, Typography } from '@material-ui/core';
|
import { Box, makeStyles, Typography } from "@material-ui/core";
|
||||||
import IpcInvokeButton from 'renderer/components/IpcInvokeButton';
|
import IpcInvokeButton from "renderer/components/IpcInvokeButton";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import StopIcon from '@material-ui/icons/Stop';
|
import StopIcon from "@material-ui/icons/Stop";
|
||||||
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
|
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
||||||
import InfoBox from '../../modal/swap/InfoBox';
|
import InfoBox from "../../modal/swap/InfoBox";
|
||||||
import CliLogsBox from '../../other/RenderedCliLog';
|
import CliLogsBox from "../../other/RenderedCliLog";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
actionsOuter: {
|
actionsOuter: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -24,10 +24,10 @@ export default function TorInfoBox() {
|
||||||
mainContent={
|
mainContent={
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
gap: '8px',
|
gap: "8px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="subtitle2">
|
<Typography variant="subtitle2">
|
||||||
|
@ -37,7 +37,7 @@ export default function TorInfoBox() {
|
||||||
below. If Tor is running, all traffic will be routed through it and
|
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.
|
the swap provider will not be able to see your IP address.
|
||||||
</Typography>
|
</Typography>
|
||||||
<CliLogsBox label="Tor Daemon Logs" logs={torStdOut.split('\n')} />
|
<CliLogsBox label="Tor Daemon Logs" logs={torStdOut.split("\n")} />
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
additionalContent={
|
additionalContent={
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Typography } from '@material-ui/core';
|
import { Typography } from "@material-ui/core";
|
||||||
import { useIsSwapRunning } from 'store/hooks';
|
import { useIsSwapRunning } from "store/hooks";
|
||||||
import HistoryTable from './table/HistoryTable';
|
import HistoryTable from "./table/HistoryTable";
|
||||||
import SwapDialog from '../../modal/swap/SwapDialog';
|
import SwapDialog from "../../modal/swap/SwapDialog";
|
||||||
import SwapTxLockAlertsBox from '../../alert/SwapTxLockAlertsBox';
|
import SwapTxLockAlertsBox from "../../alert/SwapTxLockAlertsBox";
|
||||||
|
|
||||||
export default function HistoryPage() {
|
export default function HistoryPage() {
|
||||||
const showDialog = useIsSwapRunning();
|
const showDialog = useIsSwapRunning();
|
||||||
|
|
|
@ -1,98 +1,86 @@
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Collapse,
|
Collapse,
|
||||||
IconButton,
|
IconButton,
|
||||||
makeStyles,
|
makeStyles,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import ArrowForwardIcon from "@material-ui/icons/ArrowForward";
|
import ArrowForwardIcon from "@material-ui/icons/ArrowForward";
|
||||||
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
|
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
|
||||||
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
|
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
|
||||||
import {
|
import {
|
||||||
getHumanReadableDbStateType,
|
getHumanReadableDbStateType,
|
||||||
getSwapBtcAmount,
|
getSwapBtcAmount,
|
||||||
getSwapXmrAmount,
|
getSwapXmrAmount,
|
||||||
GetSwapInfoResponse,
|
GetSwapInfoResponse,
|
||||||
} from "../../../../../models/rpcModel";
|
} from "../../../../../models/rpcModel";
|
||||||
import HistoryRowActions from "./HistoryRowActions";
|
import HistoryRowActions from "./HistoryRowActions";
|
||||||
import HistoryRowExpanded from "./HistoryRowExpanded";
|
import HistoryRowExpanded from "./HistoryRowExpanded";
|
||||||
import { BitcoinAmount, MoneroAmount } from "../../../other/Units";
|
import { BitcoinAmount, MoneroAmount } from "../../../other/Units";
|
||||||
|
|
||||||
type HistoryRowProps = {
|
type HistoryRowProps = {
|
||||||
swap: GetSwapInfoResponse;
|
swap: GetSwapInfoResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
amountTransferContainer: {
|
amountTransferContainer: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function AmountTransfer({
|
function AmountTransfer({
|
||||||
btcAmount,
|
btcAmount,
|
||||||
xmrAmount,
|
xmrAmount,
|
||||||
}: {
|
}: {
|
||||||
xmrAmount: number;
|
xmrAmount: number;
|
||||||
btcAmount: number;
|
btcAmount: number;
|
||||||
}) {
|
}) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={classes.amountTransferContainer}>
|
<Box className={classes.amountTransferContainer}>
|
||||||
<BitcoinAmount amount={btcAmount} />
|
<BitcoinAmount amount={btcAmount} />
|
||||||
<ArrowForwardIcon />
|
<ArrowForwardIcon />
|
||||||
<MoneroAmount amount={xmrAmount} />
|
<MoneroAmount amount={xmrAmount} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HistoryRow({ swap }: HistoryRowProps) {
|
export default function HistoryRow({ swap }: HistoryRowProps) {
|
||||||
const btcAmount = getSwapBtcAmount(swap);
|
const btcAmount = getSwapBtcAmount(swap);
|
||||||
const xmrAmount = getSwapXmrAmount(swap);
|
const xmrAmount = getSwapXmrAmount(swap);
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<IconButton
|
<IconButton size="small" onClick={() => setExpanded(!expanded)}>
|
||||||
size="small"
|
{expanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
|
||||||
onClick={() => setExpanded(!expanded)}
|
</IconButton>
|
||||||
>
|
</TableCell>
|
||||||
{expanded ? (
|
<TableCell>{swap.swap_id.substring(0, 5)}...</TableCell>
|
||||||
<KeyboardArrowUpIcon />
|
<TableCell>
|
||||||
) : (
|
<AmountTransfer xmrAmount={xmrAmount} btcAmount={btcAmount} />
|
||||||
<KeyboardArrowDownIcon />
|
</TableCell>
|
||||||
)}
|
<TableCell>{getHumanReadableDbStateType(swap.state_name)}</TableCell>
|
||||||
</IconButton>
|
<TableCell>
|
||||||
</TableCell>
|
<HistoryRowActions swap={swap} />
|
||||||
<TableCell>{swap.swap_id.substring(0, 5)}...</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
</TableRow>
|
||||||
<AmountTransfer
|
|
||||||
xmrAmount={xmrAmount}
|
|
||||||
btcAmount={btcAmount}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{getHumanReadableDbStateType(swap.state_name)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<HistoryRowActions swap={swap} />
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell style={{ padding: 0 }} colSpan={6}>
|
<TableCell style={{ padding: 0 }} colSpan={6}>
|
||||||
<Collapse in={expanded} timeout="auto">
|
<Collapse in={expanded} timeout="auto">
|
||||||
{expanded && <HistoryRowExpanded swap={swap} />}
|
{expanded && <HistoryRowExpanded swap={swap} />}
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,85 +6,85 @@ import { green, red } from "@material-ui/core/colors";
|
||||||
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
||||||
import IpcInvokeButton from "../../../IpcInvokeButton";
|
import IpcInvokeButton from "../../../IpcInvokeButton";
|
||||||
import {
|
import {
|
||||||
GetSwapInfoResponse,
|
GetSwapInfoResponse,
|
||||||
SwapStateName,
|
SwapStateName,
|
||||||
isSwapStateNamePossiblyCancellableSwap,
|
isSwapStateNamePossiblyCancellableSwap,
|
||||||
isSwapStateNamePossiblyRefundableSwap,
|
isSwapStateNamePossiblyRefundableSwap,
|
||||||
} from "../../../../../models/rpcModel";
|
} from "../../../../../models/rpcModel";
|
||||||
|
|
||||||
export function SwapResumeButton({
|
export function SwapResumeButton({
|
||||||
swap,
|
swap,
|
||||||
...props
|
...props
|
||||||
}: { swap: GetSwapInfoResponse } & ButtonProps) {
|
}: { swap: GetSwapInfoResponse } & ButtonProps) {
|
||||||
return (
|
return (
|
||||||
<IpcInvokeButton
|
<IpcInvokeButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={swap.completed}
|
disabled={swap.completed}
|
||||||
ipcChannel="spawn-resume-swap"
|
ipcChannel="spawn-resume-swap"
|
||||||
ipcArgs={[swap.swap_id]}
|
ipcArgs={[swap.swap_id]}
|
||||||
endIcon={<PlayArrowIcon />}
|
endIcon={<PlayArrowIcon />}
|
||||||
requiresRpc
|
requiresRpc
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
Resume
|
Resume
|
||||||
</IpcInvokeButton>
|
</IpcInvokeButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SwapCancelRefundButton({
|
export function SwapCancelRefundButton({
|
||||||
swap,
|
swap,
|
||||||
...props
|
...props
|
||||||
}: { swap: GetSwapInfoResponse } & ButtonProps) {
|
}: { swap: GetSwapInfoResponse } & ButtonProps) {
|
||||||
const cancelOrRefundable =
|
const cancelOrRefundable =
|
||||||
isSwapStateNamePossiblyCancellableSwap(swap.state_name) ||
|
isSwapStateNamePossiblyCancellableSwap(swap.state_name) ||
|
||||||
isSwapStateNamePossiblyRefundableSwap(swap.state_name);
|
isSwapStateNamePossiblyRefundableSwap(swap.state_name);
|
||||||
|
|
||||||
if (!cancelOrRefundable) {
|
if (!cancelOrRefundable) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IpcInvokeButton
|
<IpcInvokeButton
|
||||||
ipcChannel="spawn-cancel-refund"
|
ipcChannel="spawn-cancel-refund"
|
||||||
ipcArgs={[swap.swap_id]}
|
ipcArgs={[swap.swap_id]}
|
||||||
requiresRpc
|
requiresRpc
|
||||||
displayErrorSnackbar={false}
|
displayErrorSnackbar={false}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
Attempt manual Cancel & Refund
|
Attempt manual Cancel & Refund
|
||||||
</IpcInvokeButton>
|
</IpcInvokeButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HistoryRowActions({
|
export default function HistoryRowActions({
|
||||||
swap,
|
swap,
|
||||||
}: {
|
}: {
|
||||||
swap: GetSwapInfoResponse;
|
swap: GetSwapInfoResponse;
|
||||||
}) {
|
}) {
|
||||||
if (swap.state_name === SwapStateName.XmrRedeemed) {
|
if (swap.state_name === SwapStateName.XmrRedeemed) {
|
||||||
return (
|
return (
|
||||||
<Tooltip title="The swap is completed because you have redeemed the XMR">
|
<Tooltip title="The swap is completed because you have redeemed the XMR">
|
||||||
<DoneIcon style={{ color: green[500] }} />
|
<DoneIcon style={{ color: green[500] }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (swap.state_name === SwapStateName.BtcRefunded) {
|
if (swap.state_name === SwapStateName.BtcRefunded) {
|
||||||
return (
|
return (
|
||||||
<Tooltip title="The swap is completed because your BTC have been refunded">
|
<Tooltip title="The swap is completed because your BTC have been refunded">
|
||||||
<DoneIcon style={{ color: green[500] }} />
|
<DoneIcon style={{ color: green[500] }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (swap.state_name === SwapStateName.BtcPunished) {
|
if (swap.state_name === SwapStateName.BtcPunished) {
|
||||||
return (
|
return (
|
||||||
<Tooltip title="The swap is completed because you have been punished">
|
<Tooltip title="The swap is completed because you have been punished">
|
||||||
<ErrorIcon style={{ color: red[500] }} />
|
<ErrorIcon style={{ color: red[500] }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SwapResumeButton swap={swap} />;
|
return <SwapResumeButton swap={swap} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,143 +1,134 @@
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Link,
|
Link,
|
||||||
makeStyles,
|
makeStyles,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
|
import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
|
||||||
import { isTestnet } from "store/config";
|
import { isTestnet } from "store/config";
|
||||||
import {
|
import {
|
||||||
getHumanReadableDbStateType,
|
getHumanReadableDbStateType,
|
||||||
getSwapBtcAmount,
|
getSwapBtcAmount,
|
||||||
getSwapExchangeRate,
|
getSwapExchangeRate,
|
||||||
getSwapTxFees,
|
getSwapTxFees,
|
||||||
getSwapXmrAmount,
|
getSwapXmrAmount,
|
||||||
GetSwapInfoResponse,
|
GetSwapInfoResponse,
|
||||||
} from "../../../../../models/rpcModel";
|
} from "../../../../../models/rpcModel";
|
||||||
import SwapLogFileOpenButton from "./SwapLogFileOpenButton";
|
import SwapLogFileOpenButton from "./SwapLogFileOpenButton";
|
||||||
import { SwapCancelRefundButton } from "./HistoryRowActions";
|
import { SwapCancelRefundButton } from "./HistoryRowActions";
|
||||||
import { SwapMoneroRecoveryButton } from "./SwapMoneroRecoveryButton";
|
import { SwapMoneroRecoveryButton } from "./SwapMoneroRecoveryButton";
|
||||||
import {
|
import {
|
||||||
BitcoinAmount,
|
BitcoinAmount,
|
||||||
MoneroAmount,
|
MoneroAmount,
|
||||||
MoneroBitcoinExchangeRate,
|
MoneroBitcoinExchangeRate,
|
||||||
} from "renderer/components/other/Units";
|
} from "renderer/components/other/Units";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
display: "grid",
|
display: "grid",
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
actionsOuter: {
|
actionsOuter: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function HistoryRowExpanded({
|
export default function HistoryRowExpanded({
|
||||||
swap,
|
swap,
|
||||||
}: {
|
}: {
|
||||||
swap: GetSwapInfoResponse;
|
swap: GetSwapInfoResponse;
|
||||||
}) {
|
}) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const { seller, start_date: startDate } = swap;
|
const { seller, start_date: startDate } = swap;
|
||||||
const btcAmount = getSwapBtcAmount(swap);
|
const btcAmount = getSwapBtcAmount(swap);
|
||||||
const xmrAmount = getSwapXmrAmount(swap);
|
const xmrAmount = getSwapXmrAmount(swap);
|
||||||
const txFees = getSwapTxFees(swap);
|
const txFees = getSwapTxFees(swap);
|
||||||
const exchangeRate = getSwapExchangeRate(swap);
|
const exchangeRate = getSwapExchangeRate(swap);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={classes.outer}>
|
<Box className={classes.outer}>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Started on</TableCell>
|
<TableCell>Started on</TableCell>
|
||||||
<TableCell>{startDate}</TableCell>
|
<TableCell>{startDate}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Swap ID</TableCell>
|
<TableCell>Swap ID</TableCell>
|
||||||
<TableCell>{swap.swap_id}</TableCell>
|
<TableCell>{swap.swap_id}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>State Name</TableCell>
|
<TableCell>State Name</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{getHumanReadableDbStateType(swap.state_name)}
|
{getHumanReadableDbStateType(swap.state_name)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Monero Amount</TableCell>
|
<TableCell>Monero Amount</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<MoneroAmount amount={xmrAmount} />
|
<MoneroAmount amount={xmrAmount} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Bitcoin Amount</TableCell>
|
<TableCell>Bitcoin Amount</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<BitcoinAmount amount={btcAmount} />
|
<BitcoinAmount amount={btcAmount} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Exchange Rate</TableCell>
|
<TableCell>Exchange Rate</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<MoneroBitcoinExchangeRate
|
<MoneroBitcoinExchangeRate rate={exchangeRate} />
|
||||||
rate={exchangeRate}
|
</TableCell>
|
||||||
/>
|
</TableRow>
|
||||||
</TableCell>
|
<TableRow>
|
||||||
</TableRow>
|
<TableCell>Bitcoin Network Fees</TableCell>
|
||||||
<TableRow>
|
<TableCell>
|
||||||
<TableCell>Bitcoin Network Fees</TableCell>
|
<BitcoinAmount amount={txFees} />
|
||||||
<TableCell>
|
</TableCell>
|
||||||
<BitcoinAmount amount={txFees} />
|
</TableRow>
|
||||||
</TableCell>
|
<TableRow>
|
||||||
</TableRow>
|
<TableCell>Provider Address</TableCell>
|
||||||
<TableRow>
|
<TableCell>
|
||||||
<TableCell>Provider Address</TableCell>
|
<Box>{seller.addresses.join(", ")}</Box>
|
||||||
<TableCell>
|
</TableCell>
|
||||||
<Box>{seller.addresses.join(", ")}</Box>
|
</TableRow>
|
||||||
</TableCell>
|
<TableRow>
|
||||||
</TableRow>
|
<TableCell>Bitcoin lock transaction</TableCell>
|
||||||
<TableRow>
|
<TableCell>
|
||||||
<TableCell>Bitcoin lock transaction</TableCell>
|
<Link
|
||||||
<TableCell>
|
href={getBitcoinTxExplorerUrl(swap.tx_lock_id, isTestnet())}
|
||||||
<Link
|
target="_blank"
|
||||||
href={getBitcoinTxExplorerUrl(
|
>
|
||||||
swap.tx_lock_id,
|
{swap.tx_lock_id}
|
||||||
isTestnet(),
|
</Link>
|
||||||
)}
|
</TableCell>
|
||||||
target="_blank"
|
</TableRow>
|
||||||
>
|
</TableBody>
|
||||||
{swap.tx_lock_id}
|
</Table>
|
||||||
</Link>
|
</TableContainer>
|
||||||
</TableCell>
|
<Box className={classes.actionsOuter}>
|
||||||
</TableRow>
|
<SwapLogFileOpenButton
|
||||||
</TableBody>
|
swapId={swap.swap_id}
|
||||||
</Table>
|
variant="outlined"
|
||||||
</TableContainer>
|
size="small"
|
||||||
<Box className={classes.actionsOuter}>
|
/>
|
||||||
<SwapLogFileOpenButton
|
<SwapCancelRefundButton swap={swap} variant="contained" size="small" />
|
||||||
swapId={swap.swap_id}
|
<SwapMoneroRecoveryButton
|
||||||
variant="outlined"
|
swap={swap}
|
||||||
size="small"
|
variant="contained"
|
||||||
/>
|
size="small"
|
||||||
<SwapCancelRefundButton
|
/>
|
||||||
swap={swap}
|
</Box>
|
||||||
variant="contained"
|
</Box>
|
||||||
size="small"
|
);
|
||||||
/>
|
|
||||||
<SwapMoneroRecoveryButton
|
|
||||||
swap={swap}
|
|
||||||
variant="contained"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,53 @@
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
makeStyles,
|
makeStyles,
|
||||||
Paper,
|
Paper,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableHead,
|
TableHead,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { sortBy } from "lodash";
|
import { sortBy } from "lodash";
|
||||||
import { parseDateString } from "utils/parseUtils";
|
import { parseDateString } from "utils/parseUtils";
|
||||||
import {
|
import {
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
useSwapInfosSortedByDate,
|
useSwapInfosSortedByDate,
|
||||||
} from "../../../../../store/hooks";
|
} from "../../../../../store/hooks";
|
||||||
import HistoryRow from "./HistoryRow";
|
import HistoryRow from "./HistoryRow";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
paddingTop: theme.spacing(1),
|
paddingTop: theme.spacing(1),
|
||||||
paddingBottom: theme.spacing(1),
|
paddingBottom: theme.spacing(1),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function HistoryTable() {
|
export default function HistoryTable() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const swapSortedByDate = useSwapInfosSortedByDate();
|
const swapSortedByDate = useSwapInfosSortedByDate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={classes.outer}>
|
<Box className={classes.outer}>
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell />
|
<TableCell />
|
||||||
<TableCell>ID</TableCell>
|
<TableCell>ID</TableCell>
|
||||||
<TableCell>Amount</TableCell>
|
<TableCell>Amount</TableCell>
|
||||||
<TableCell>State</TableCell>
|
<TableCell>State</TableCell>
|
||||||
<TableCell />
|
<TableCell />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{swapSortedByDate.map((swap) => (
|
{swapSortedByDate.map((swap) => (
|
||||||
<HistoryRow swap={swap} key={swap.swap_id} />
|
<HistoryRow swap={swap} key={swap.swap_id} />
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { ButtonProps } from '@material-ui/core/Button/Button';
|
import { ButtonProps } from "@material-ui/core/Button/Button";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@material-ui/core';
|
} from "@material-ui/core";
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import { CliLog } from 'models/cliModel';
|
import { CliLog } from "models/cliModel";
|
||||||
import IpcInvokeButton from '../../../IpcInvokeButton';
|
import IpcInvokeButton from "../../../IpcInvokeButton";
|
||||||
import CliLogsBox from '../../../other/RenderedCliLog';
|
import CliLogsBox from "../../../other/RenderedCliLog";
|
||||||
|
|
||||||
export default function SwapLogFileOpenButton({
|
export default function SwapLogFileOpenButton({
|
||||||
swapId,
|
swapId,
|
||||||
|
|
|
@ -1,120 +1,119 @@
|
||||||
import { ButtonProps } from "@material-ui/core/Button/Button";
|
import { ButtonProps } from "@material-ui/core/Button/Button";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
Link,
|
Link,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { useAppDispatch, useAppSelector } from "store/hooks";
|
import { useAppDispatch, useAppSelector } from "store/hooks";
|
||||||
import { rpcResetMoneroRecoveryKeys } from "store/features/rpcSlice";
|
import { rpcResetMoneroRecoveryKeys } from "store/features/rpcSlice";
|
||||||
import {
|
import {
|
||||||
GetSwapInfoResponse,
|
GetSwapInfoResponse,
|
||||||
isSwapMoneroRecoverable,
|
isSwapMoneroRecoverable,
|
||||||
} from "../../../../../models/rpcModel";
|
} from "../../../../../models/rpcModel";
|
||||||
import IpcInvokeButton from "../../../IpcInvokeButton";
|
import IpcInvokeButton from "../../../IpcInvokeButton";
|
||||||
import DialogHeader from "../../../modal/DialogHeader";
|
import DialogHeader from "../../../modal/DialogHeader";
|
||||||
import ScrollablePaperTextBox from "../../../other/ScrollablePaperTextBox";
|
import ScrollablePaperTextBox from "../../../other/ScrollablePaperTextBox";
|
||||||
|
|
||||||
function MoneroRecoveryKeysDialog({ swap }: { swap: GetSwapInfoResponse }) {
|
function MoneroRecoveryKeysDialog({ swap }: { swap: GetSwapInfoResponse }) {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const keys = useAppSelector((s) => s.rpc.state.moneroRecovery);
|
const keys = useAppSelector((s) => s.rpc.state.moneroRecovery);
|
||||||
|
|
||||||
function onClose() {
|
function onClose() {
|
||||||
dispatch(rpcResetMoneroRecoveryKeys());
|
dispatch(rpcResetMoneroRecoveryKeys());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keys === null || keys.swapId !== swap.swap_id) {
|
if (keys === null || keys.swapId !== swap.swap_id) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onClose={onClose} maxWidth="sm" fullWidth>
|
<Dialog open onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
<DialogHeader
|
<DialogHeader
|
||||||
title={`Recovery Keys for swap ${swap.swap_id.substring(0, 5)}...`}
|
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>
|
</Box>
|
||||||
You can use the keys below to manually redeem the Monero
|
</DialogContent>
|
||||||
funds from the multi-signature wallet.
|
<DialogActions>
|
||||||
<ul>
|
<Button onClick={onClose} color="primary" variant="contained">
|
||||||
<li>
|
Done
|
||||||
This is useful if the swap daemon fails to redeem
|
</Button>
|
||||||
the funds itself
|
</DialogActions>
|
||||||
</li>
|
</Dialog>
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SwapMoneroRecoveryButton({
|
export function SwapMoneroRecoveryButton({
|
||||||
swap,
|
swap,
|
||||||
...props
|
...props
|
||||||
}: { swap: GetSwapInfoResponse } & ButtonProps) {
|
}: { swap: GetSwapInfoResponse } & ButtonProps) {
|
||||||
const isRecoverable = isSwapMoneroRecoverable(swap.state_name);
|
const isRecoverable = isSwapMoneroRecoverable(swap.state_name);
|
||||||
|
|
||||||
if (!isRecoverable) {
|
if (!isRecoverable) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IpcInvokeButton
|
<IpcInvokeButton
|
||||||
ipcChannel="spawn-monero-recovery"
|
ipcChannel="spawn-monero-recovery"
|
||||||
ipcArgs={[swap.swap_id]}
|
ipcArgs={[swap.swap_id]}
|
||||||
requiresRpc
|
requiresRpc
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
Display Monero Recovery Keys
|
Display Monero Recovery Keys
|
||||||
</IpcInvokeButton>
|
</IpcInvokeButton>
|
||||||
<MoneroRecoveryKeysDialog swap={swap} />
|
<MoneroRecoveryKeysDialog swap={swap} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Box } from '@material-ui/core';
|
import { Box } from "@material-ui/core";
|
||||||
import { Alert, AlertTitle } from '@material-ui/lab';
|
import { Alert, AlertTitle } from "@material-ui/lab";
|
||||||
import { removeAlert } from 'store/features/alertsSlice';
|
import { removeAlert } from "store/features/alertsSlice";
|
||||||
import { useAppDispatch, useAppSelector } from 'store/hooks';
|
import { useAppDispatch, useAppSelector } from "store/hooks";
|
||||||
|
|
||||||
export default function ApiAlertsBox() {
|
export default function ApiAlertsBox() {
|
||||||
const alerts = useAppSelector((state) => state.alerts.alerts);
|
const alerts = useAppSelector((state) => state.alerts.alerts);
|
||||||
|
@ -14,7 +14,7 @@ export default function ApiAlertsBox() {
|
||||||
if (alerts.length === 0) return null;
|
if (alerts.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box style={{ display: 'flex', justifyContent: 'center', gap: '1rem' }}>
|
<Box style={{ display: "flex", justifyContent: "center", gap: "1rem" }}>
|
||||||
{alerts.map((alert) => (
|
{alerts.map((alert) => (
|
||||||
<Alert
|
<Alert
|
||||||
variant="filled"
|
variant="filled"
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { Box, makeStyles } from '@material-ui/core';
|
import { Box, makeStyles } from "@material-ui/core";
|
||||||
import SwapWidget from './SwapWidget';
|
import SwapWidget from "./SwapWidget";
|
||||||
import ApiAlertsBox from './ApiAlertsBox';
|
import ApiAlertsBox from "./ApiAlertsBox";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
paddingBottom: theme.spacing(1),
|
paddingBottom: theme.spacing(1),
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChangeEvent, useEffect, useState } from 'react';
|
import { ChangeEvent, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
makeStyles,
|
makeStyles,
|
||||||
Box,
|
Box,
|
||||||
|
@ -7,21 +7,21 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
Fab,
|
Fab,
|
||||||
} from '@material-ui/core';
|
} from "@material-ui/core";
|
||||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||||
import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward';
|
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
|
||||||
import SwapHorizIcon from '@material-ui/icons/SwapHoriz';
|
import SwapHorizIcon from "@material-ui/icons/SwapHoriz";
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from "@material-ui/lab";
|
||||||
import { satsToBtc } from 'utils/conversionUtils';
|
import { satsToBtc } from "utils/conversionUtils";
|
||||||
import { useAppSelector } from 'store/hooks';
|
import { useAppSelector } from "store/hooks";
|
||||||
import { ExtendedProviderStatus } from 'models/apiModel';
|
import { ExtendedProviderStatus } from "models/apiModel";
|
||||||
import { isSwapState } from 'models/storeModel';
|
import { isSwapState } from "models/storeModel";
|
||||||
import SwapDialog from '../../modal/swap/SwapDialog';
|
import SwapDialog from "../../modal/swap/SwapDialog";
|
||||||
import ProviderSelect from '../../modal/provider/ProviderSelect';
|
import ProviderSelect from "../../modal/provider/ProviderSelect";
|
||||||
import {
|
import {
|
||||||
ListSellersDialogOpenButton,
|
ListSellersDialogOpenButton,
|
||||||
ProviderSubmitDialogOpenButton,
|
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
|
// After RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN failed reconnection attempts we can assume the public registry is down
|
||||||
const RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN = 1;
|
const RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN = 1;
|
||||||
|
@ -32,9 +32,9 @@ function isRegistryDown(reconnectionAttempts: number): boolean {
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
inner: {
|
inner: {
|
||||||
width: 'min(480px, 100%)',
|
width: "min(480px, 100%)",
|
||||||
minHeight: '150px',
|
minHeight: "150px",
|
||||||
display: 'grid',
|
display: "grid",
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
gridGap: theme.spacing(1),
|
gridGap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
|
@ -48,19 +48,19 @@ const useStyles = makeStyles((theme) => ({
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
},
|
},
|
||||||
swapIconOuter: {
|
swapIconOuter: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
swapIcon: {
|
swapIcon: {
|
||||||
marginRight: theme.spacing(1),
|
marginRight: theme.spacing(1),
|
||||||
},
|
},
|
||||||
noProvidersAlertOuter: {
|
noProvidersAlertOuter: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
noProvidersAlertButtonsOuter: {
|
noProvidersAlertButtonsOuter: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -111,7 +111,7 @@ function HasProviderSwapWidget({
|
||||||
function getBtcFieldError(): string | null {
|
function getBtcFieldError(): string | null {
|
||||||
const parsedBtcAmount = Number(btcFieldValue);
|
const parsedBtcAmount = Number(btcFieldValue);
|
||||||
if (Number.isNaN(parsedBtcAmount)) {
|
if (Number.isNaN(parsedBtcAmount)) {
|
||||||
return 'This is not a valid number';
|
return "This is not a valid number";
|
||||||
}
|
}
|
||||||
if (parsedBtcAmount < satsToBtc(selectedProvider.minSwapAmount)) {
|
if (parsedBtcAmount < satsToBtc(selectedProvider.minSwapAmount)) {
|
||||||
return `The minimum swap amount is ${satsToBtc(
|
return `The minimum swap amount is ${satsToBtc(
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Box, makeStyles, Typography } from '@material-ui/core';
|
import { Box, makeStyles, Typography } from "@material-ui/core";
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from "@material-ui/lab";
|
||||||
import WithdrawWidget from './WithdrawWidget';
|
import WithdrawWidget from "./WithdrawWidget";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
gridGap: theme.spacing(0.5),
|
gridGap: theme.spacing(0.5),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import { Box, Button, makeStyles, Typography } from '@material-ui/core';
|
import { Box, Button, makeStyles, Typography } from "@material-ui/core";
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import SendIcon from '@material-ui/icons/Send';
|
import SendIcon from "@material-ui/icons/Send";
|
||||||
import { useAppSelector, useIsRpcEndpointBusy } from 'store/hooks';
|
import { useAppSelector, useIsRpcEndpointBusy } from "store/hooks";
|
||||||
import { RpcMethod } from 'models/rpcModel';
|
import { RpcMethod } from "models/rpcModel";
|
||||||
import BitcoinIcon from '../../icons/BitcoinIcon';
|
import BitcoinIcon from "../../icons/BitcoinIcon";
|
||||||
import WithdrawDialog from '../../modal/wallet/WithdrawDialog';
|
import WithdrawDialog from "../../modal/wallet/WithdrawDialog";
|
||||||
import WalletRefreshButton from './WalletRefreshButton';
|
import WalletRefreshButton from "./WalletRefreshButton";
|
||||||
import InfoBox from '../../modal/swap/InfoBox';
|
import InfoBox from "../../modal/swap/InfoBox";
|
||||||
import { SatsAmount } from 'renderer/components/other/Units';
|
import { SatsAmount } from "renderer/components/other/Units";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
title: {
|
title: {
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: theme.spacing(0.5),
|
gap: theme.spacing(0.5),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -3,14 +3,14 @@ import {
|
||||||
SnackbarKey,
|
SnackbarKey,
|
||||||
SnackbarProvider,
|
SnackbarProvider,
|
||||||
useSnackbar,
|
useSnackbar,
|
||||||
} from 'notistack';
|
} from "notistack";
|
||||||
import { IconButton, styled } from '@material-ui/core';
|
import { IconButton, styled } from "@material-ui/core";
|
||||||
import { Close } from '@material-ui/icons';
|
import { Close } from "@material-ui/icons";
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
const StyledMaterialDesignContent = styled(MaterialDesignContent)(() => ({
|
const StyledMaterialDesignContent = styled(MaterialDesignContent)(() => ({
|
||||||
'&.notistack-MuiContent': {
|
"&.notistack-MuiContent": {
|
||||||
maxWidth: '50vw',
|
maxWidth: "50vw",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue