mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-04-25 18:29:09 -04:00
feat: swap history tauri connector
This commit is contained in:
parent
cdd6635c8f
commit
2e1b6f6b43
103
Cargo.lock
generated
103
Cargo.lock
generated
@ -1601,15 +1601,6 @@ version = "0.3.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding_rs"
|
|
||||||
version = "0.8.34"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enum-as-inner"
|
name = "enum-as-inner"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@ -2763,25 +2754,6 @@ dependencies = [
|
|||||||
"nom",
|
"nom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is-docker"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
|
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is-wsl"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
|
|
||||||
dependencies = [
|
|
||||||
"is-docker",
|
|
||||||
"once_cell",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
@ -4089,17 +4061,6 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "open"
|
|
||||||
version = "5.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9d2c909a3fce3bd80efef4cd1c6c056bd9376a8fe06fcfdbebaf32cb485a7e37"
|
|
||||||
dependencies = [
|
|
||||||
"is-wsl",
|
|
||||||
"libc",
|
|
||||||
"pathdiff",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "open-metrics-client"
|
name = "open-metrics-client"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@ -4135,16 +4096,6 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "os_pipe"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"windows-sys 0.52.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -5932,16 +5883,6 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "shared_child"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shell-words"
|
name = "shell-words"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -6729,44 +6670,6 @@ dependencies = [
|
|||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tauri-plugin"
|
|
||||||
version = "2.0.0-rc.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "51a5c65ab8536a7e27b70ecbb0713ab42e8508acd9af1bc4a0817ccf7caf3165"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"glob",
|
|
||||||
"plist",
|
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tauri-utils",
|
|
||||||
"toml 0.8.2",
|
|
||||||
"walkdir",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tauri-plugin-shell"
|
|
||||||
version = "2.0.0-rc.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9209f6c32caec61e156a5616f7d80ba7683ca4a0a5641cbe5d3086ab371aaab2"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_rs",
|
|
||||||
"log",
|
|
||||||
"open",
|
|
||||||
"os_pipe",
|
|
||||||
"regex",
|
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"shared_child",
|
|
||||||
"tauri",
|
|
||||||
"tauri-plugin",
|
|
||||||
"thiserror",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.0.0-rc.1"
|
version = "2.0.0-rc.1"
|
||||||
@ -7683,7 +7586,7 @@ dependencies = [
|
|||||||
"swap",
|
"swap",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-shell",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -7731,9 +7634,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.9.1"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
|
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.15",
|
"getrandom 0.2.15",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
import { piconerosToXmr, satsToBtc } from 'utils/conversionUtils';
|
import { piconerosToXmr, satsToBtc } from "utils/conversionUtils";
|
||||||
import { exhaustiveGuard } from 'utils/typescriptUtils';
|
import { exhaustiveGuard } from "utils/typescriptUtils";
|
||||||
|
|
||||||
export enum RpcMethod {
|
export enum RpcMethod {
|
||||||
GET_BTC_BALANCE = 'get_bitcoin_balance',
|
GET_BTC_BALANCE = "get_bitcoin_balance",
|
||||||
WITHDRAW_BTC = 'withdraw_btc',
|
WITHDRAW_BTC = "withdraw_btc",
|
||||||
BUY_XMR = 'buy_xmr',
|
BUY_XMR = "buy_xmr",
|
||||||
RESUME_SWAP = 'resume_swap',
|
RESUME_SWAP = "resume_swap",
|
||||||
LIST_SELLERS = 'list_sellers',
|
LIST_SELLERS = "list_sellers",
|
||||||
CANCEL_REFUND_SWAP = 'cancel_refund_swap',
|
CANCEL_REFUND_SWAP = "cancel_refund_swap",
|
||||||
GET_SWAP_INFO = 'get_swap_info',
|
GET_SWAP_INFO = "get_swap_info",
|
||||||
SUSPEND_CURRENT_SWAP = 'suspend_current_swap',
|
SUSPEND_CURRENT_SWAP = "suspend_current_swap",
|
||||||
GET_HISTORY = 'get_history',
|
GET_HISTORY = "get_history",
|
||||||
GET_MONERO_RECOVERY_KEYS = 'get_monero_recovery_info',
|
GET_MONERO_RECOVERY_KEYS = "get_monero_recovery_info",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum RpcProcessStateType {
|
export enum RpcProcessStateType {
|
||||||
STARTED = 'starting...',
|
STARTED = "starting...",
|
||||||
LISTENING_FOR_CONNECTIONS = 'running',
|
LISTENING_FOR_CONNECTIONS = "running",
|
||||||
EXITED = 'exited',
|
EXITED = "exited",
|
||||||
NOT_STARTED = 'not started',
|
NOT_STARTED = "not started",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RawRpcResponseSuccess<T> = {
|
export type RawRpcResponseSuccess<T> = {
|
||||||
@ -38,13 +38,13 @@ export type RawRpcResponse<T> = RawRpcResponseSuccess<T> | RawRpcResponseError;
|
|||||||
export function isSuccessResponse<T>(
|
export function isSuccessResponse<T>(
|
||||||
response: RawRpcResponse<T>,
|
response: RawRpcResponse<T>,
|
||||||
): response is RawRpcResponseSuccess<T> {
|
): response is RawRpcResponseSuccess<T> {
|
||||||
return 'result' in response;
|
return "result" in response;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isErrorResponse<T>(
|
export function isErrorResponse<T>(
|
||||||
response: RawRpcResponse<T>,
|
response: RawRpcResponse<T>,
|
||||||
): response is RawRpcResponseError {
|
): response is RawRpcResponseError {
|
||||||
return 'error' in response;
|
return "error" in response;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RpcSellerStatus {
|
export interface RpcSellerStatus {
|
||||||
@ -56,7 +56,7 @@ export interface RpcSellerStatus {
|
|||||||
max_quantity: number;
|
max_quantity: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| 'Unreachable';
|
| "Unreachable";
|
||||||
multiaddr: string;
|
multiaddr: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export type SwapTimelockInfoCancelled = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SwapTimelockInfoPunished = 'Punish';
|
export type SwapTimelockInfoPunished = "Punish";
|
||||||
|
|
||||||
export type SwapTimelockInfo =
|
export type SwapTimelockInfo =
|
||||||
| SwapTimelockInfoNone
|
| SwapTimelockInfoNone
|
||||||
@ -90,19 +90,19 @@ export type SwapTimelockInfo =
|
|||||||
export function isSwapTimelockInfoNone(
|
export function isSwapTimelockInfoNone(
|
||||||
info: SwapTimelockInfo,
|
info: SwapTimelockInfo,
|
||||||
): info is SwapTimelockInfoNone {
|
): info is SwapTimelockInfoNone {
|
||||||
return typeof info === 'object' && 'None' in info;
|
return typeof info === "object" && "None" in info;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSwapTimelockInfoCancelled(
|
export function isSwapTimelockInfoCancelled(
|
||||||
info: SwapTimelockInfo,
|
info: SwapTimelockInfo,
|
||||||
): info is SwapTimelockInfoCancelled {
|
): info is SwapTimelockInfoCancelled {
|
||||||
return typeof info === 'object' && 'Cancel' in info;
|
return typeof info === "object" && "Cancel" in info;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSwapTimelockInfoPunished(
|
export function isSwapTimelockInfoPunished(
|
||||||
info: SwapTimelockInfo,
|
info: SwapTimelockInfo,
|
||||||
): info is SwapTimelockInfoPunished {
|
): info is SwapTimelockInfoPunished {
|
||||||
return info === 'Punish';
|
return info === "Punish";
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SwapSellerInfo = {
|
export type SwapSellerInfo = {
|
||||||
@ -111,21 +111,21 @@ export type SwapSellerInfo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface GetSwapInfoResponse {
|
export interface GetSwapInfoResponse {
|
||||||
swapId: string;
|
swap_id: string;
|
||||||
completed: boolean;
|
completed: boolean;
|
||||||
seller: SwapSellerInfo;
|
seller: SwapSellerInfo;
|
||||||
startDate: string;
|
start_date: string;
|
||||||
stateName: SwapStateName;
|
state_name: SwapStateName;
|
||||||
timelock: null | SwapTimelockInfo;
|
timelock: null | SwapTimelockInfo;
|
||||||
txLockId: string;
|
tx_lock_id: string;
|
||||||
txCancelFee: number;
|
tx_cancel_fee: number;
|
||||||
txRefundFee: number;
|
tx_refund_fee: number;
|
||||||
txLockFee: number;
|
tx_lock_fee: number;
|
||||||
btcAmount: number;
|
btc_amount: number;
|
||||||
xmrAmount: number;
|
xmr_amount: number;
|
||||||
btcRefundAddress: string;
|
btc_refund_address: string;
|
||||||
cancelTimelock: number;
|
cancel_timelock: number;
|
||||||
punishTimelock: number;
|
punish_timelock: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MoneroRecoveryResponse = {
|
export type MoneroRecoveryResponse = {
|
||||||
@ -144,19 +144,19 @@ export interface GetHistoryResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum SwapStateName {
|
export enum SwapStateName {
|
||||||
Started = 'quote has been requested',
|
Started = "quote has been requested",
|
||||||
SwapSetupCompleted = 'execution setup done',
|
SwapSetupCompleted = "execution setup done",
|
||||||
BtcLocked = 'btc is locked',
|
BtcLocked = "btc is locked",
|
||||||
XmrLockProofReceived = 'XMR lock transaction transfer proof received',
|
XmrLockProofReceived = "XMR lock transaction transfer proof received",
|
||||||
XmrLocked = 'xmr is locked',
|
XmrLocked = "xmr is locked",
|
||||||
EncSigSent = 'encrypted signature is sent',
|
EncSigSent = "encrypted signature is sent",
|
||||||
BtcRedeemed = 'btc is redeemed',
|
BtcRedeemed = "btc is redeemed",
|
||||||
CancelTimelockExpired = 'cancel timelock is expired',
|
CancelTimelockExpired = "cancel timelock is expired",
|
||||||
BtcCancelled = 'btc is cancelled',
|
BtcCancelled = "btc is cancelled",
|
||||||
BtcRefunded = 'btc is refunded',
|
BtcRefunded = "btc is refunded",
|
||||||
XmrRedeemed = 'xmr is redeemed',
|
XmrRedeemed = "xmr is redeemed",
|
||||||
BtcPunished = 'btc is punished',
|
BtcPunished = "btc is punished",
|
||||||
SafelyAborted = 'safely aborted',
|
SafelyAborted = "safely aborted",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SwapStateNameRunningSwap = Exclude<
|
export type SwapStateNameRunningSwap = Exclude<
|
||||||
@ -275,7 +275,7 @@ export function isSwapStateNamePossiblyRefundableSwap(
|
|||||||
export function isGetSwapInfoResponseRunningSwap(
|
export function isGetSwapInfoResponseRunningSwap(
|
||||||
response: GetSwapInfoResponse,
|
response: GetSwapInfoResponse,
|
||||||
): response is GetSwapInfoResponseRunningSwap {
|
): response is GetSwapInfoResponseRunningSwap {
|
||||||
return isSwapStateNameRunningSwap(response.stateName);
|
return isSwapStateNameRunningSwap(response.state_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSwapMoneroRecoverable(swapStateName: SwapStateName): boolean {
|
export function isSwapMoneroRecoverable(swapStateName: SwapStateName): boolean {
|
||||||
@ -286,46 +286,46 @@ export function isSwapMoneroRecoverable(swapStateName: SwapStateName): boolean {
|
|||||||
export function getHumanReadableDbStateType(type: SwapStateName): string {
|
export function getHumanReadableDbStateType(type: SwapStateName): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SwapStateName.Started:
|
case SwapStateName.Started:
|
||||||
return 'Quote has been requested';
|
return "Quote has been requested";
|
||||||
case SwapStateName.SwapSetupCompleted:
|
case SwapStateName.SwapSetupCompleted:
|
||||||
return 'Swap has been initiated';
|
return "Swap has been initiated";
|
||||||
case SwapStateName.BtcLocked:
|
case SwapStateName.BtcLocked:
|
||||||
return 'Bitcoin has been locked';
|
return "Bitcoin has been locked";
|
||||||
case SwapStateName.XmrLockProofReceived:
|
case SwapStateName.XmrLockProofReceived:
|
||||||
return 'Monero lock transaction transfer proof has been received';
|
return "Monero lock transaction transfer proof has been received";
|
||||||
case SwapStateName.XmrLocked:
|
case SwapStateName.XmrLocked:
|
||||||
return 'Monero has been locked';
|
return "Monero has been locked";
|
||||||
case SwapStateName.EncSigSent:
|
case SwapStateName.EncSigSent:
|
||||||
return 'Encrypted signature has been sent';
|
return "Encrypted signature has been sent";
|
||||||
case SwapStateName.BtcRedeemed:
|
case SwapStateName.BtcRedeemed:
|
||||||
return 'Bitcoin has been redeemed';
|
return "Bitcoin has been redeemed";
|
||||||
case SwapStateName.CancelTimelockExpired:
|
case SwapStateName.CancelTimelockExpired:
|
||||||
return 'Cancel timelock has expired';
|
return "Cancel timelock has expired";
|
||||||
case SwapStateName.BtcCancelled:
|
case SwapStateName.BtcCancelled:
|
||||||
return 'Swap has been cancelled';
|
return "Swap has been cancelled";
|
||||||
case SwapStateName.BtcRefunded:
|
case SwapStateName.BtcRefunded:
|
||||||
return 'Bitcoin has been refunded';
|
return "Bitcoin has been refunded";
|
||||||
case SwapStateName.XmrRedeemed:
|
case SwapStateName.XmrRedeemed:
|
||||||
return 'Monero has been redeemed';
|
return "Monero has been redeemed";
|
||||||
case SwapStateName.BtcPunished:
|
case SwapStateName.BtcPunished:
|
||||||
return 'Bitcoin has been punished';
|
return "Bitcoin has been punished";
|
||||||
case SwapStateName.SafelyAborted:
|
case SwapStateName.SafelyAborted:
|
||||||
return 'Swap has been safely aborted';
|
return "Swap has been safely aborted";
|
||||||
default:
|
default:
|
||||||
return exhaustiveGuard(type);
|
return exhaustiveGuard(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSwapTxFees(swap: GetSwapInfoResponse): number {
|
export function getSwapTxFees(swap: GetSwapInfoResponse): number {
|
||||||
return satsToBtc(swap.txLockFee);
|
return satsToBtc(swap.tx_lock_fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSwapBtcAmount(swap: GetSwapInfoResponse): number {
|
export function getSwapBtcAmount(swap: GetSwapInfoResponse): number {
|
||||||
return satsToBtc(swap.btcAmount);
|
return satsToBtc(swap.btc_amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSwapXmrAmount(swap: GetSwapInfoResponse): number {
|
export function getSwapXmrAmount(swap: GetSwapInfoResponse): number {
|
||||||
return piconerosToXmr(swap.xmrAmount);
|
return piconerosToXmr(swap.xmr_amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSwapExchangeRate(swap: GetSwapInfoResponse): number {
|
export function getSwapExchangeRate(swap: GetSwapInfoResponse): number {
|
||||||
|
@ -1,97 +1,103 @@
|
|||||||
import { makeStyles } from '@material-ui/core';
|
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.punishTimelock;
|
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 might
|
The swap provider has taken a long time to lock their Monero. This
|
||||||
mean that:
|
might mean that:
|
||||||
<ul className={classes.list}>
|
<ul className={classes.list}>
|
||||||
<li>
|
<li>
|
||||||
There is a technical issue that prevents them from locking their funds
|
There is a technical issue that prevents them from locking
|
||||||
</li>
|
their funds
|
||||||
<li>They are a malicious actor (unlikely)</li>
|
</li>
|
||||||
</ul>
|
<li>They are a malicious actor (unlikely)</li>
|
||||||
<br />
|
</ul>
|
||||||
There is still hope for the swap to be successful but you have to be extra
|
<br />
|
||||||
careful. Regardless of why it has taken them so long, it is important that
|
There is still hope for the swap to be successful but you have to be
|
||||||
you refund the swap within the required time period if the swap is not
|
extra careful. Regardless of why it has taken them so long, it is
|
||||||
completed. If you fail to to do so, you will be punished and lose your
|
important that you refund the swap within the required time period
|
||||||
money.
|
if the swap is not completed. If you fail to to do so, you will be
|
||||||
<ul className={classes.list}>
|
punished and lose your money.
|
||||||
{isSwapTimelockInfoNone(timelock) && (
|
<ul className={classes.list}>
|
||||||
<>
|
{isSwapTimelockInfoNone(timelock) && (
|
||||||
<li>
|
<>
|
||||||
<strong>
|
<li>
|
||||||
You will be able to refund in about{' '}
|
<strong>
|
||||||
<HumanizedBitcoinBlockDuration
|
You will be able to refund in about{" "}
|
||||||
blocks={timelock.None.blocks_left}
|
<HumanizedBitcoinBlockDuration
|
||||||
/>
|
blocks={timelock.None.blocks_left}
|
||||||
</strong>
|
/>
|
||||||
</li>
|
</strong>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<strong>
|
<strong>
|
||||||
If you have not refunded or completed the swap in about{' '}
|
If you have not refunded or completed the swap
|
||||||
<HumanizedBitcoinBlockDuration
|
in about{" "}
|
||||||
blocks={timelock.None.blocks_left + punishTimelockOffset}
|
<HumanizedBitcoinBlockDuration
|
||||||
/>
|
blocks={
|
||||||
, you will lose your funds.
|
timelock.None.blocks_left +
|
||||||
</strong>
|
punishTimelockOffset
|
||||||
</li>
|
}
|
||||||
</>
|
/>
|
||||||
)}
|
, you will lose your funds.
|
||||||
{isSwapTimelockInfoCancelled(timelock) && (
|
</strong>
|
||||||
<li>
|
</li>
|
||||||
<strong>
|
</>
|
||||||
If you have not refunded or completed the swap in about{' '}
|
)}
|
||||||
<HumanizedBitcoinBlockDuration
|
{isSwapTimelockInfoCancelled(timelock) && (
|
||||||
blocks={timelock.Cancel.blocks_left}
|
<li>
|
||||||
/>
|
<strong>
|
||||||
, you will lose your funds.
|
If you have not refunded or completed the swap in
|
||||||
</strong>
|
about{" "}
|
||||||
</li>
|
<HumanizedBitcoinBlockDuration
|
||||||
)}
|
blocks={timelock.Cancel.blocks_left}
|
||||||
<li>
|
/>
|
||||||
As long as you see this screen, the swap will be refunded
|
, you will lose your funds.
|
||||||
automatically when the time comes. If this fails, you have to manually
|
</strong>
|
||||||
refund by navigating to the History page.
|
</li>
|
||||||
</li>
|
)}
|
||||||
</ul>
|
<li>
|
||||||
</Alert>
|
As long as you see this screen, the swap will be refunded
|
||||||
);
|
automatically when the time comes. If this fails, you have
|
||||||
|
to manually refund by navigating to the History page.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,35 @@
|
|||||||
import { Alert, AlertTitle } from '@material-ui/lab/';
|
import { Alert, AlertTitle } from "@material-ui/lab/";
|
||||||
import { Box, makeStyles } from '@material-ui/core';
|
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,20 +55,24 @@ 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 swap={swap} size="small" variant="contained" />
|
<SwapMoneroRecoveryButton
|
||||||
</Box>
|
swap={swap}
|
||||||
);
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,28 +82,31 @@ 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 approximately{' '}
|
Your Bitcoin is locked. If the swap is not completed in
|
||||||
<HumanizedBitcoinBlockDuration blocks={timelock.None.blocks_left} />,
|
approximately{" "}
|
||||||
you need to refund
|
<HumanizedBitcoinBlockDuration
|
||||||
</>,
|
blocks={timelock.None.blocks_left}
|
||||||
<>
|
/>
|
||||||
You will lose your funds if you do not refund or complete the swap
|
, you need to refund
|
||||||
within{' '}
|
</>,
|
||||||
<HumanizedBitcoinBlockDuration
|
<>
|
||||||
blocks={timelock.None.blocks_left + punishTimelockOffset}
|
You will lose your funds if you do not refund or complete the
|
||||||
/>
|
swap within{" "}
|
||||||
</>,
|
<HumanizedBitcoinBlockDuration
|
||||||
]}
|
blocks={timelock.None.blocks_left + punishTimelockOffset}
|
||||||
/>
|
/>
|
||||||
|
</>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,30 +117,34 @@ 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"
|
||||||
<SwapCancelRefundButton swap={swap} size="small" variant="contained" />
|
variant="contained"
|
||||||
</Box>
|
/>
|
||||||
);
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,7 +152,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</>
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -150,55 +161,55 @@ const ImmediateActionAlert = () => (
|
|||||||
* @returns JSX.Element | null
|
* @returns JSX.Element | null
|
||||||
*/
|
*/
|
||||||
function SwapAlertStatusText({
|
function SwapAlertStatusText({
|
||||||
swap,
|
swap,
|
||||||
}: {
|
}: {
|
||||||
swap: GetSwapInfoResponseRunningSwap;
|
swap: GetSwapInfoResponseRunningSwap;
|
||||||
}) {
|
}) {
|
||||||
switch (swap.stateName) {
|
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.punishTimelock}
|
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.stateName);
|
return exhaustiveGuard(swap.state_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -207,27 +218,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.swapId}
|
key={swap.swap_id}
|
||||||
severity="warning"
|
severity="warning"
|
||||||
action={<SwapResumeButton swap={swap} />}
|
action={<SwapResumeButton swap={swap} />}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
>
|
>
|
||||||
<AlertTitle>
|
<AlertTitle>
|
||||||
Swap {swap.swapId.substring(0, 5)}... is unfinished
|
Swap {swap.swap_id.substring(0, 5)}... is unfinished
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
<SwapAlertStatusText swap={swap} />
|
<SwapAlertStatusText swap={swap} />
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
import { Box, makeStyles } from '@material-ui/core';
|
import { Box, makeStyles } from "@material-ui/core";
|
||||||
import { useSwapInfosSortedByDate } from 'store/hooks';
|
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.swapId} swap={swap} />
|
<SwapStatusAlert key={swap.swap_id} swap={swap} />
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,44 @@
|
|||||||
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";
|
||||||
import {
|
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
||||||
useActiveSwapInfo,
|
import { parseDateString } from "utils/parseUtils";
|
||||||
useAppSelector,
|
import { store } from "renderer/store/storeRenderer";
|
||||||
} from 'store/hooks';
|
import { CliLog } from "models/cliModel";
|
||||||
import { parseDateString } from 'utils/parseUtils';
|
import { submitFeedbackViaHttp } from "../../../api";
|
||||||
import { store } from 'renderer/store/storeRenderer';
|
import { PiconeroAmount } from "../../other/Units";
|
||||||
import { CliLog } from 'models/cliModel';
|
import LoadingButton from "../../other/LoadingButton";
|
||||||
import { submitFeedbackViaHttp } from '../../../api';
|
|
||||||
import { PiconeroAmount } from '../../other/Units';
|
|
||||||
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")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
attachedBody = `${JSON.stringify(swapInfo, null, 4)} \n\nLogs: ${logs
|
await submitFeedbackViaHttp(body, attachedBody);
|
||||||
.map((l) => JSON.stringify(l))
|
|
||||||
.join('\n====\n')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await submitFeedbackViaHttp(body, attachedBody);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -51,120 +48,136 @@ 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.swapId}>
|
<MenuItem value={swap.swap_id}>
|
||||||
Swap {swap.swapId.substring(0, 5)}... from{' '}
|
Swap {swap.swap_id.substring(0, 5)}... from{" "}
|
||||||
{new Date(parseDateString(swap.startDate)).toDateString()} (
|
{new Date(parseDateString(swap.start_date)).toDateString()}{" "}
|
||||||
<PiconeroAmount amount={swap.xmrAmount} />)
|
(
|
||||||
</MenuItem>
|
<PiconeroAmount amount={swap.xmr_amount} />)
|
||||||
))}
|
</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?.swapId || 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 issue
|
Got something to say? Drop us a message below. If you had an
|
||||||
with a specific swap, select it from the dropdown to attach the logs.
|
issue with a specific swap, select it from the dropdown to
|
||||||
It will help us figure out what went wrong. Hit that submit button
|
attach the logs. It will help us figure out what went wrong.
|
||||||
when you are ready. We appreciate you taking the time to share your
|
Hit that submit button when you are ready. We appreciate you
|
||||||
thoughts!
|
taking the time to share your thoughts!
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<Box style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
<Box
|
||||||
<TextField
|
style={{
|
||||||
variant="outlined"
|
display: "flex",
|
||||||
value={bodyText}
|
flexDirection: "column",
|
||||||
onChange={(e) => setBodyText(e.target.value)}
|
gap: "1rem",
|
||||||
label={
|
}}
|
||||||
bodyTooLong
|
>
|
||||||
? `Text is too long (${bodyText.length}/${MAX_FEEDBACK_LENGTH})`
|
<TextField
|
||||||
: 'Feedback'
|
variant="outlined"
|
||||||
}
|
value={bodyText}
|
||||||
multiline
|
onChange={(e) => setBodyText(e.target.value)}
|
||||||
minRows={4}
|
label={
|
||||||
maxRows={4}
|
bodyTooLong
|
||||||
fullWidth
|
? `Text is too long (${bodyText.length}/${MAX_FEEDBACK_LENGTH})`
|
||||||
error={bodyTooLong}
|
: "Feedback"
|
||||||
/>
|
}
|
||||||
<SwapSelectDropDown
|
multiline
|
||||||
selectedSwap={selectedAttachedSwap}
|
minRows={4}
|
||||||
setSelectedSwap={setSelectedAttachedSwap}
|
maxRows={4}
|
||||||
/>
|
fullWidth
|
||||||
</Box>
|
error={bodyTooLong}
|
||||||
</DialogContent>
|
/>
|
||||||
<DialogActions>
|
<SwapSelectDropDown
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
selectedSwap={selectedAttachedSwap}
|
||||||
<LoadingButton
|
setSelectedSwap={setSelectedAttachedSwap}
|
||||||
color="primary"
|
/>
|
||||||
variant="contained"
|
</Box>
|
||||||
onClick={async () => {
|
</DialogContent>
|
||||||
if (pending) {
|
<DialogActions>
|
||||||
return;
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
}
|
<LoadingButton
|
||||||
|
color="primary"
|
||||||
|
variant="contained"
|
||||||
|
onClick={async () => {
|
||||||
|
if (pending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setPending(true);
|
setPending(true);
|
||||||
await submitFeedback(bodyText, selectedAttachedSwap);
|
await submitFeedback(
|
||||||
enqueueSnackbar('Feedback submitted successfully!', {
|
bodyText,
|
||||||
variant: 'success',
|
selectedAttachedSwap,
|
||||||
});
|
);
|
||||||
} catch (e) {
|
enqueueSnackbar(
|
||||||
console.error(`Failed to submit feedback: ${e}`);
|
"Feedback submitted successfully!",
|
||||||
enqueueSnackbar(`Failed to submit feedback (${e})`, {
|
{
|
||||||
variant: 'error',
|
variant: "success",
|
||||||
});
|
},
|
||||||
} finally {
|
);
|
||||||
setPending(false);
|
} catch (e) {
|
||||||
}
|
console.error(`Failed to submit feedback: ${e}`);
|
||||||
onClose();
|
enqueueSnackbar(
|
||||||
}}
|
`Failed to submit feedback (${e})`,
|
||||||
loading={pending}
|
{
|
||||||
>
|
variant: "error",
|
||||||
Submit
|
},
|
||||||
</LoadingButton>
|
);
|
||||||
</DialogActions>
|
} finally {
|
||||||
</Dialog>
|
setPending(false);
|
||||||
);
|
}
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
loading={pending}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</LoadingButton>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,166 +1,169 @@
|
|||||||
import { Step, StepLabel, Stepper, Typography } from '@material-ui/core';
|
import { Step, StepLabel, Stepper, Typography } from "@material-ui/core";
|
||||||
import { SwapSpawnType } from 'models/cliModel';
|
import { SwapSpawnType } from "models/cliModel";
|
||||||
import { SwapStateName } from 'models/rpcModel';
|
import { SwapStateName } from "models/rpcModel";
|
||||||
import { useActiveSwapInfo, useAppSelector } from 'store/hooks';
|
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()?.stateName ?? 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(stateName, processExited);
|
const [pathType, activeStep, error] = getActiveStep(
|
||||||
|
stateName,
|
||||||
|
processExited,
|
||||||
|
);
|
||||||
|
|
||||||
// If the current swap is being manually cancelled and refund, we want to show the unhappy path even though the current state is not a "unhappy" state
|
// If 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,43 +1,44 @@
|
|||||||
import { Box, DialogContentText } from '@material-ui/core';
|
import { Box, DialogContentText } from "@material-ui/core";
|
||||||
import { SwapStateBtcRefunded } from 'models/storeModel';
|
import { SwapStateBtcRefunded } from "models/storeModel";
|
||||||
import { useActiveSwapInfo } from 'store/hooks';
|
import { useActiveSwapInfo } from "store/hooks";
|
||||||
import BitcoinTransactionInfoBox from '../../BitcoinTransactionInfoBox';
|
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.btcRefundAddress}`
|
? `Refund address: ${swap.btc_refund_address}`
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
Unfortunately, the swap was not successful. However, rest assured that
|
Unfortunately, the swap was not successful. However, rest
|
||||||
all your Bitcoin has been refunded to the specified address. The swap
|
assured that all your Bitcoin has been refunded to the specified
|
||||||
process is now complete, and you are free to exit the application.
|
address. The swap process is now complete, and you are free to
|
||||||
</DialogContentText>
|
exit the application.
|
||||||
<Box
|
</DialogContentText>
|
||||||
style={{
|
<Box
|
||||||
display: 'flex',
|
style={{
|
||||||
flexDirection: 'column',
|
display: "flex",
|
||||||
gap: '0.5rem',
|
flexDirection: "column",
|
||||||
}}
|
gap: "0.5rem",
|
||||||
>
|
}}
|
||||||
{state && (
|
>
|
||||||
<BitcoinTransactionInfoBox
|
{state && (
|
||||||
title="Bitcoin Refund Transaction"
|
<BitcoinTransactionInfoBox
|
||||||
txId={state.bobBtcRefundTxId}
|
title="Bitcoin Refund Transaction"
|
||||||
loading={false}
|
txId={state.bobBtcRefundTxId}
|
||||||
additionalContent={additionalContent}
|
loading={false}
|
||||||
/>
|
additionalContent={additionalContent}
|
||||||
)}
|
/>
|
||||||
<FeedbackInfoBox />
|
)}
|
||||||
</Box>
|
<FeedbackInfoBox />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,71 +1,71 @@
|
|||||||
import { Box, DialogContentText } from '@material-ui/core';
|
import { Box, DialogContentText } from "@material-ui/core";
|
||||||
import { useActiveSwapInfo, useAppSelector } from 'store/hooks';
|
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
||||||
import { SwapStateProcessExited } from 'models/storeModel';
|
import { SwapStateProcessExited } from "models/storeModel";
|
||||||
import CliLogsBox from '../../../../other/RenderedCliLog';
|
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(
|
|
||||||
isCancelRefund
|
|
||||||
? 'The manual cancel and refund was unsuccessful.'
|
|
||||||
: 'The swap exited unexpectedly without completing.',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!hasSwap && !isCancelRefund) {
|
|
||||||
messages.push('No funds were locked.');
|
|
||||||
}
|
|
||||||
|
|
||||||
messages.push(
|
|
||||||
hasRpcError
|
|
||||||
? 'Check the error and the logs below for more information.'
|
|
||||||
: 'Check the logs below for more information.',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasSwap) {
|
|
||||||
messages.push(`The swap is in the "${swap.stateName}" state.`);
|
|
||||||
if (!isCancelRefund) {
|
|
||||||
messages.push(
|
messages.push(
|
||||||
'Try resuming the swap or attempt to initiate a manual cancel and refund.',
|
isCancelRefund
|
||||||
|
? "The manual cancel and refund was unsuccessful."
|
||||||
|
: "The swap exited unexpectedly without completing.",
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
if (!hasSwap && !isCancelRefund) {
|
||||||
|
messages.push("No funds were locked.");
|
||||||
|
}
|
||||||
|
|
||||||
|
messages.push(
|
||||||
|
hasRpcError
|
||||||
|
? "Check the error and the logs below for more information."
|
||||||
|
: "Check the logs below for more information.",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasSwap) {
|
||||||
|
messages.push(`The swap is in the "${swap.state_name}" state.`);
|
||||||
|
if (!isCancelRefund) {
|
||||||
|
messages.push(
|
||||||
|
"Try resuming the swap or attempt to initiate a manual cancel and refund.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages.join(' ');
|
return (
|
||||||
}
|
<Box>
|
||||||
|
<DialogContentText>{getText()}</DialogContentText>
|
||||||
return (
|
<Box
|
||||||
<Box>
|
style={{
|
||||||
<DialogContentText>{getText()}</DialogContentText>
|
display: "flex",
|
||||||
<Box
|
flexDirection: "column",
|
||||||
style={{
|
gap: "0.5rem",
|
||||||
display: 'flex',
|
}}
|
||||||
flexDirection: 'column',
|
>
|
||||||
gap: '0.5rem',
|
{state.rpcError && (
|
||||||
}}
|
<CliLogsBox
|
||||||
>
|
logs={[state.rpcError]}
|
||||||
{state.rpcError && (
|
label="Error returned by the Swap Daemon"
|
||||||
<CliLogsBox
|
/>
|
||||||
logs={[state.rpcError]}
|
)}
|
||||||
label="Error returned by the Swap Daemon"
|
<CliLogsBox logs={logs} label="Logs relevant to the swap" />
|
||||||
/>
|
</Box>
|
||||||
)}
|
</Box>
|
||||||
<CliLogsBox logs={logs} label="Logs relevant to the swap" />
|
);
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,47 @@
|
|||||||
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";
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import SwapStatePage from '../SwapStatePage';
|
import SwapStatePage from "../SwapStatePage";
|
||||||
import BitcoinRefundedPage from '../done/BitcoinRefundedPage';
|
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 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) {
|
||||||
if (swap.stateName === SwapStateName.XmrRedeemed) {
|
if (swap.state_name === SwapStateName.XmrRedeemed) {
|
||||||
return <XmrRedeemInMempoolPage state={null} />;
|
return <XmrRedeemInMempoolPage state={null} />;
|
||||||
|
}
|
||||||
|
if (swap.state_name === SwapStateName.BtcRefunded) {
|
||||||
|
return <BitcoinRefundedPage state={null} />;
|
||||||
|
}
|
||||||
|
if (swap.state_name === SwapStateName.BtcPunished) {
|
||||||
|
return <BitcoinPunishedPage />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (swap.stateName === SwapStateName.BtcRefunded) {
|
|
||||||
return <BitcoinRefundedPage state={null} />;
|
|
||||||
}
|
|
||||||
if (swap.stateName === 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,86 +1,98 @@
|
|||||||
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 size="small" onClick={() => setExpanded(!expanded)}>
|
<IconButton
|
||||||
{expanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
|
size="small"
|
||||||
</IconButton>
|
onClick={() => setExpanded(!expanded)}
|
||||||
</TableCell>
|
>
|
||||||
<TableCell>{swap.swapId.substring(0, 5)}...</TableCell>
|
{expanded ? (
|
||||||
<TableCell>
|
<KeyboardArrowUpIcon />
|
||||||
<AmountTransfer xmrAmount={xmrAmount} btcAmount={btcAmount} />
|
) : (
|
||||||
</TableCell>
|
<KeyboardArrowDownIcon />
|
||||||
<TableCell>{getHumanReadableDbStateType(swap.stateName)}</TableCell>
|
)}
|
||||||
<TableCell>
|
</IconButton>
|
||||||
<HistoryRowActions swap={swap} />
|
</TableCell>
|
||||||
</TableCell>
|
<TableCell>{swap.swap_id.substring(0, 5)}...</TableCell>
|
||||||
</TableRow>
|
<TableCell>
|
||||||
|
<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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,90 +1,90 @@
|
|||||||
import { Tooltip } from '@material-ui/core';
|
import { Tooltip } from "@material-ui/core";
|
||||||
import Button, { ButtonProps } from '@material-ui/core/Button/Button';
|
import Button, { ButtonProps } from "@material-ui/core/Button/Button";
|
||||||
import DoneIcon from '@material-ui/icons/Done';
|
import DoneIcon from "@material-ui/icons/Done";
|
||||||
import ErrorIcon from '@material-ui/icons/Error';
|
import ErrorIcon from "@material-ui/icons/Error";
|
||||||
import { green, red } from '@material-ui/core/colors';
|
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.swapId]}
|
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.stateName) ||
|
isSwapStateNamePossiblyCancellableSwap(swap.state_name) ||
|
||||||
isSwapStateNamePossiblyRefundableSwap(swap.stateName);
|
isSwapStateNamePossiblyRefundableSwap(swap.state_name);
|
||||||
|
|
||||||
if (!cancelOrRefundable) {
|
if (!cancelOrRefundable) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IpcInvokeButton
|
<IpcInvokeButton
|
||||||
ipcChannel="spawn-cancel-refund"
|
ipcChannel="spawn-cancel-refund"
|
||||||
ipcArgs={[swap.swapId]}
|
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.stateName === 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.stateName === 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.stateName === 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,134 +1,143 @@
|
|||||||
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, 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.swapId}</TableCell>
|
<TableCell>{swap.swap_id}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>State Name</TableCell>
|
<TableCell>State Name</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{getHumanReadableDbStateType(swap.stateName)}
|
{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 rate={exchangeRate} />
|
<MoneroBitcoinExchangeRate
|
||||||
</TableCell>
|
rate={exchangeRate}
|
||||||
</TableRow>
|
/>
|
||||||
<TableRow>
|
</TableCell>
|
||||||
<TableCell>Bitcoin Network Fees</TableCell>
|
</TableRow>
|
||||||
<TableCell>
|
<TableRow>
|
||||||
<BitcoinAmount amount={txFees} />
|
<TableCell>Bitcoin Network Fees</TableCell>
|
||||||
</TableCell>
|
<TableCell>
|
||||||
</TableRow>
|
<BitcoinAmount amount={txFees} />
|
||||||
<TableRow>
|
</TableCell>
|
||||||
<TableCell>Provider Address</TableCell>
|
</TableRow>
|
||||||
<TableCell>
|
<TableRow>
|
||||||
<Box>{seller.addresses.join(', ')}</Box>
|
<TableCell>Provider Address</TableCell>
|
||||||
</TableCell>
|
<TableCell>
|
||||||
</TableRow>
|
<Box>{seller.addresses.join(", ")}</Box>
|
||||||
<TableRow>
|
</TableCell>
|
||||||
<TableCell>Bitcoin lock transaction</TableCell>
|
</TableRow>
|
||||||
<TableCell>
|
<TableRow>
|
||||||
<Link
|
<TableCell>Bitcoin lock transaction</TableCell>
|
||||||
href={getBitcoinTxExplorerUrl(swap.txLockId, isTestnet())}
|
<TableCell>
|
||||||
target="_blank"
|
<Link
|
||||||
>
|
href={getBitcoinTxExplorerUrl(
|
||||||
{swap.txLockId}
|
swap.tx_lock_id,
|
||||||
</Link>
|
isTestnet(),
|
||||||
</TableCell>
|
)}
|
||||||
</TableRow>
|
target="_blank"
|
||||||
</TableBody>
|
>
|
||||||
</Table>
|
{swap.tx_lock_id}
|
||||||
</TableContainer>
|
</Link>
|
||||||
<Box className={classes.actionsOuter}>
|
</TableCell>
|
||||||
<SwapLogFileOpenButton
|
</TableRow>
|
||||||
swapId={swap.swapId}
|
</TableBody>
|
||||||
variant="outlined"
|
</Table>
|
||||||
size="small"
|
</TableContainer>
|
||||||
/>
|
<Box className={classes.actionsOuter}>
|
||||||
<SwapCancelRefundButton swap={swap} variant="contained" size="small" />
|
<SwapLogFileOpenButton
|
||||||
<SwapMoneroRecoveryButton
|
swapId={swap.swap_id}
|
||||||
swap={swap}
|
variant="outlined"
|
||||||
variant="contained"
|
size="small"
|
||||||
size="small"
|
/>
|
||||||
/>
|
<SwapCancelRefundButton
|
||||||
</Box>
|
swap={swap}
|
||||||
</Box>
|
variant="contained"
|
||||||
);
|
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.swapId} />
|
<HistoryRow swap={swap} key={swap.swap_id} />
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,119 +1,120 @@
|
|||||||
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.swapId) {
|
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.swapId.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>
|
||||||
</Box>
|
<DialogContentText>
|
||||||
</DialogContent>
|
You can use the keys below to manually redeem the Monero
|
||||||
<DialogActions>
|
funds from the multi-signature wallet.
|
||||||
<Button onClick={onClose} color="primary" variant="contained">
|
<ul>
|
||||||
Done
|
<li>
|
||||||
</Button>
|
This is useful if the swap daemon fails to redeem
|
||||||
</DialogActions>
|
the funds itself
|
||||||
</Dialog>
|
</li>
|
||||||
);
|
<li>
|
||||||
|
If you have come this far, there is no risk of
|
||||||
|
losing funds. You are the only one with access to
|
||||||
|
these keys and can use them to access your funds
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
View{" "}
|
||||||
|
<Link
|
||||||
|
href="https://www.getmonero.org/resources/user-guides/restore_from_keys.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
this guide
|
||||||
|
</Link>{" "}
|
||||||
|
for a detailed description on how to import the keys
|
||||||
|
and spend the funds.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</DialogContentText>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "0.5rem",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
["Primary Address", keys.keys.address],
|
||||||
|
["View Key", keys.keys.view_key],
|
||||||
|
["Spend Key", keys.keys.spend_key],
|
||||||
|
["Restore Height", keys.keys.restore_height.toString()],
|
||||||
|
].map(([title, value]) => (
|
||||||
|
<ScrollablePaperTextBox
|
||||||
|
minHeight="2rem"
|
||||||
|
title={title}
|
||||||
|
copyValue={value}
|
||||||
|
rows={[value]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onClose} color="primary" variant="contained">
|
||||||
|
Done
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SwapMoneroRecoveryButton({
|
export function SwapMoneroRecoveryButton({
|
||||||
swap,
|
swap,
|
||||||
...props
|
...props
|
||||||
}: { swap: GetSwapInfoResponse } & ButtonProps) {
|
}: { swap: GetSwapInfoResponse } & ButtonProps) {
|
||||||
const isRecoverable = isSwapMoneroRecoverable(swap.stateName);
|
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.swapId]}
|
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 { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { BalanceBitcoinResponse } from "models/rpcModel";
|
import { BalanceBitcoinResponse } from "models/rpcModel";
|
||||||
import { store } from "./store/storeRenderer";
|
import { store } from "./store/storeRenderer";
|
||||||
import { rpcSetBalance } from "store/features/rpcSlice";
|
import { rpcSetBalance, rpcSetSwapInfo } from "store/features/rpcSlice";
|
||||||
|
|
||||||
export async function checkBitcoinBalance() {
|
export async function checkBitcoinBalance() {
|
||||||
// TODO: use tauri-bindgen here
|
// TODO: use tauri-bindgen here
|
||||||
@ -13,6 +13,7 @@ export async function checkBitcoinBalance() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getRawSwapInfos() {
|
export async function getRawSwapInfos() {
|
||||||
const response = await invoke("swap_infos");
|
const response = await invoke("swap_infos_all");
|
||||||
console.log(response);
|
console.log(response);
|
||||||
|
(response as any[]).forEach((info) => store.dispatch(rpcSetSwapInfo(info)));
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { ExtendedProviderStatus, ProviderStatus } from 'models/apiModel';
|
import { ExtendedProviderStatus, ProviderStatus } from "models/apiModel";
|
||||||
import { MoneroWalletRpcUpdateState } from 'models/storeModel';
|
import { MoneroWalletRpcUpdateState } from "models/storeModel";
|
||||||
import {
|
import {
|
||||||
GetSwapInfoResponse,
|
GetSwapInfoResponse,
|
||||||
MoneroRecoveryResponse,
|
MoneroRecoveryResponse,
|
||||||
RpcProcessStateType,
|
RpcProcessStateType,
|
||||||
} from '../../models/rpcModel';
|
} from "../../models/rpcModel";
|
||||||
import {
|
import {
|
||||||
CliLog,
|
CliLog,
|
||||||
isCliLog,
|
isCliLog,
|
||||||
@ -14,8 +14,8 @@ import {
|
|||||||
isCliLogFinishedSyncingMoneroWallet,
|
isCliLogFinishedSyncingMoneroWallet,
|
||||||
isCliLogStartedRpcServer,
|
isCliLogStartedRpcServer,
|
||||||
isCliLogStartedSyncingMoneroWallet,
|
isCliLogStartedSyncingMoneroWallet,
|
||||||
} from '../../models/cliModel';
|
} from "../../models/cliModel";
|
||||||
import { getLogsAndStringsFromRawFileString } from 'utils/parseUtils';
|
import { getLogsAndStringsFromRawFileString } from "utils/parseUtils";
|
||||||
|
|
||||||
type Process =
|
type Process =
|
||||||
| {
|
| {
|
||||||
@ -82,7 +82,7 @@ const initialState: RPCSlice = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const rpcSlice = createSlice({
|
export const rpcSlice = createSlice({
|
||||||
name: 'rpc',
|
name: "rpc",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
rpcAddLogs(slice, action: PayloadAction<(CliLog | string)[]>) {
|
rpcAddLogs(slice, action: PayloadAction<(CliLog | string)[]>) {
|
||||||
@ -110,7 +110,7 @@ export const rpcSlice = createSlice({
|
|||||||
downloadUrl: log.fields.download_url,
|
downloadUrl: log.fields.download_url,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (log.fields.progress === '100%') {
|
if (log.fields.progress === "100%") {
|
||||||
slice.state.moneroWalletRpc.updateState = false;
|
slice.state.moneroWalletRpc.updateState = false;
|
||||||
}
|
}
|
||||||
} else if (isCliLogStartedSyncingMoneroWallet(log)) {
|
} else if (isCliLogStartedSyncingMoneroWallet(log)) {
|
||||||
@ -169,7 +169,7 @@ export const rpcSlice = createSlice({
|
|||||||
slice.state.withdrawTxId = null;
|
slice.state.withdrawTxId = null;
|
||||||
},
|
},
|
||||||
rpcSetSwapInfo(slice, action: PayloadAction<GetSwapInfoResponse>) {
|
rpcSetSwapInfo(slice, action: PayloadAction<GetSwapInfoResponse>) {
|
||||||
slice.state.swapInfos[action.payload.swapId] = action.payload;
|
slice.state.swapInfos[action.payload.swap_id] = action.payload;
|
||||||
},
|
},
|
||||||
rpcSetEndpointBusy(slice, action: PayloadAction<string>) {
|
rpcSetEndpointBusy(slice, action: PayloadAction<string>) {
|
||||||
if (!slice.busyEndpoints.includes(action.payload)) {
|
if (!slice.busyEndpoints.includes(action.payload)) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
|
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||||
import type { AppDispatch, RootState } from 'renderer/store/storeRenderer';
|
import type { AppDispatch, RootState } from "renderer/store/storeRenderer";
|
||||||
import { sortBy } from 'lodash';
|
import { sortBy } from "lodash";
|
||||||
import { parseDateString } from 'utils/parseUtils';
|
import { parseDateString } from "utils/parseUtils";
|
||||||
|
|
||||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||||
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||||
@ -22,7 +22,7 @@ export function useIsSwapRunning() {
|
|||||||
|
|
||||||
export function useSwapInfo(swapId: string | null) {
|
export function useSwapInfo(swapId: string | null) {
|
||||||
return useAppSelector((state) =>
|
return useAppSelector((state) =>
|
||||||
swapId ? state.rpc.state.swapInfos[swapId] ?? null : null,
|
swapId ? (state.rpc.state.swapInfos[swapId] ?? null) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +51,6 @@ export function useSwapInfosSortedByDate() {
|
|||||||
const swapInfos = useAppSelector((state) => state.rpc.state.swapInfos);
|
const swapInfos = useAppSelector((state) => state.rpc.state.swapInfos);
|
||||||
return sortBy(
|
return sortBy(
|
||||||
Object.values(swapInfos),
|
Object.values(swapInfos),
|
||||||
(swap) => -parseDateString(swap.startDate),
|
(swap) => -parseDateString(swap.start_date),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,4 +21,4 @@ serde = { version = "1", features = ["derive"] }
|
|||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
swap = { path = "../swap" }
|
swap = { path = "../swap" }
|
||||||
tauri = { version = "2.0.0-rc.1", features = ["config-json5"] }
|
tauri = { version = "2.0.0-rc.1", features = ["config-json5"] }
|
||||||
tauri-plugin-shell = "2.0.0-rc.0"
|
uuid = "1.10.0"
|
||||||
|
@ -3,5 +3,5 @@
|
|||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "Capability for the main window",
|
"description": "Capability for the main window",
|
||||||
"windows": ["main"],
|
"windows": ["main"],
|
||||||
"permissions": ["shell:allow-open"]
|
"permissions": []
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,15 @@ use std::sync::Arc;
|
|||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use swap::{
|
use swap::{
|
||||||
api::{
|
api::{
|
||||||
request::{get_balance, BalanceArgs, BalanceResponse},
|
request::{
|
||||||
|
get_balance, get_swap_info, get_swap_infos_all, BalanceArgs, BalanceResponse,
|
||||||
|
GetSwapInfoResponse,
|
||||||
|
},
|
||||||
Context,
|
Context,
|
||||||
},
|
},
|
||||||
cli::command::{Bitcoin, Monero},
|
cli::command::{Bitcoin, Monero},
|
||||||
};
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
// Lazy load the Context
|
// Lazy load the Context
|
||||||
static CONTEXT: OnceCell<Arc<Context>> = OnceCell::new();
|
static CONTEXT: OnceCell<Arc<Context>> = OnceCell::new();
|
||||||
@ -26,6 +30,15 @@ async fn balance() -> Result<BalanceResponse, String> {
|
|||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn swap_infos_all() -> Result<Vec<GetSwapInfoResponse>, String> {
|
||||||
|
let context = CONTEXT.get().unwrap();
|
||||||
|
|
||||||
|
get_swap_infos_all(context.clone())
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
tauri::async_runtime::block_on(async {
|
tauri::async_runtime::block_on(async {
|
||||||
let context = Context::build(
|
let context = Context::build(
|
||||||
@ -56,8 +69,7 @@ fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box<dyn std::error::Error>>
|
|||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_shell::init())
|
.invoke_handler(tauri::generate_handler![balance, swap_infos_all])
|
||||||
.invoke_handler(tauri::generate_handler![balance])
|
|
||||||
.setup(setup)
|
.setup(setup)
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
@ -7,6 +7,7 @@ use crate::network::swarm;
|
|||||||
use crate::protocol::bob::{BobState, Swap};
|
use crate::protocol::bob::{BobState, Swap};
|
||||||
use crate::protocol::{bob, State};
|
use crate::protocol::{bob, State};
|
||||||
use crate::{bitcoin, cli, monero, rpc};
|
use crate::{bitcoin, cli, monero, rpc};
|
||||||
|
use ::bitcoin::Txid;
|
||||||
use anyhow::{bail, Context as AnyContext, Result};
|
use anyhow::{bail, Context as AnyContext, Result};
|
||||||
use libp2p::core::Multiaddr;
|
use libp2p::core::Multiaddr;
|
||||||
use qrcode::render::unicode;
|
use qrcode::render::unicode;
|
||||||
@ -52,22 +53,6 @@ pub struct MoneroRecoveryArgs {
|
|||||||
pub swap_id: Uuid,
|
pub swap_id: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct ResumeSwapResponse {
|
|
||||||
pub result: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct BalanceResponse {
|
|
||||||
pub balance: u64, // in satoshis
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct BuyXmrResponse {
|
|
||||||
pub swap_id: String,
|
|
||||||
pub quote: BidQuote, // You'll need to import or define BidQuote
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub struct WithdrawBtcArgs {
|
pub struct WithdrawBtcArgs {
|
||||||
pub amount: Option<Amount>,
|
pub amount: Option<Amount>,
|
||||||
@ -94,6 +79,52 @@ pub struct GetSwapInfoArgs {
|
|||||||
pub swap_id: Uuid,
|
pub swap_id: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct ResumeSwapResponse {
|
||||||
|
pub result: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct BalanceResponse {
|
||||||
|
pub balance: u64, // in satoshis
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct BuyXmrResponse {
|
||||||
|
pub swap_id: String,
|
||||||
|
pub quote: BidQuote, // You'll need to import or define BidQuote
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetHistoryResponse {
|
||||||
|
swaps: Vec<(Uuid, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct GetSwapInfoResponse {
|
||||||
|
pub swap_id: Uuid,
|
||||||
|
pub seller: Seller,
|
||||||
|
pub completed: bool,
|
||||||
|
pub start_date: String,
|
||||||
|
pub state_name: String,
|
||||||
|
pub xmr_amount: u64,
|
||||||
|
pub btc_amount: u64,
|
||||||
|
pub tx_lock_id: Txid,
|
||||||
|
pub tx_cancel_fee: u64,
|
||||||
|
pub tx_refund_fee: u64,
|
||||||
|
pub tx_lock_fee: u64,
|
||||||
|
pub btc_refund_address: String,
|
||||||
|
pub cancel_timelock: u32,
|
||||||
|
pub punish_timelock: u32,
|
||||||
|
pub timelock: Option<ExpiredTimelocks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Seller {
|
||||||
|
pub peer_id: String,
|
||||||
|
pub addresses: Vec<Multiaddr>,
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: We probably dont even need this.
|
// TODO: We probably dont even need this.
|
||||||
// We can just call the method directly from the RPC server, the CLI and the Tauri connector
|
// We can just call the method directly from the RPC server, the CLI and the Tauri connector
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -231,8 +262,23 @@ async fn suspend_current_swap(context: Arc<Context>) -> Result<serde_json::Value
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_swap_infos_all(context: Arc<Context>) -> Result<Vec<GetSwapInfoResponse>> {
|
||||||
|
let swap_ids = context.db.all().await?;
|
||||||
|
let mut swap_infos = Vec::new();
|
||||||
|
|
||||||
|
for (swap_id, _) in swap_ids {
|
||||||
|
let swap_info = get_swap_info(GetSwapInfoArgs { swap_id }, context.clone()).await?;
|
||||||
|
swap_infos.push(swap_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(swap_infos)
|
||||||
|
}
|
||||||
|
|
||||||
// #[tracing::instrument(fields(method="get_swap_info", swap_id = args.swap_id), skip(context))]
|
// #[tracing::instrument(fields(method="get_swap_info", swap_id = args.swap_id), skip(context))]
|
||||||
async fn get_swap_info(args: GetSwapInfoArgs, context: Arc<Context>) -> Result<serde_json::Value> {
|
pub async fn get_swap_info(
|
||||||
|
args: GetSwapInfoArgs,
|
||||||
|
context: Arc<Context>,
|
||||||
|
) -> Result<GetSwapInfoResponse> {
|
||||||
let bitcoin_wallet = context
|
let bitcoin_wallet = context
|
||||||
.bitcoin_wallet
|
.bitcoin_wallet
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -311,38 +357,38 @@ async fn get_swap_info(args: GetSwapInfoArgs, context: Arc<Context>) -> Result<s
|
|||||||
}
|
}
|
||||||
BobState::BtcLocked { state3: state, .. }
|
BobState::BtcLocked { state3: state, .. }
|
||||||
| BobState::XmrLockProofReceived { state, .. } => {
|
| BobState::XmrLockProofReceived { state, .. } => {
|
||||||
Some(state.expired_timelock(bitcoin_wallet).await)
|
Some(state.expired_timelock(bitcoin_wallet).await?)
|
||||||
}
|
}
|
||||||
BobState::XmrLocked(state) | BobState::EncSigSent(state) => {
|
BobState::XmrLocked(state) | BobState::EncSigSent(state) => {
|
||||||
Some(state.expired_timelock(bitcoin_wallet).await)
|
Some(state.expired_timelock(bitcoin_wallet).await?)
|
||||||
}
|
}
|
||||||
BobState::CancelTimelockExpired(state) | BobState::BtcCancelled(state) => {
|
BobState::CancelTimelockExpired(state) | BobState::BtcCancelled(state) => {
|
||||||
Some(state.expired_timelock(bitcoin_wallet).await)
|
Some(state.expired_timelock(bitcoin_wallet).await?)
|
||||||
}
|
}
|
||||||
BobState::BtcPunished { .. } => Some(Ok(ExpiredTimelocks::Punish)),
|
BobState::BtcPunished { .. } => Some(ExpiredTimelocks::Punish),
|
||||||
BobState::BtcRefunded(_) | BobState::BtcRedeemed(_) | BobState::XmrRedeemed { .. } => None,
|
BobState::BtcRefunded(_) | BobState::BtcRedeemed(_) | BobState::XmrRedeemed { .. } => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(json!({
|
Ok(GetSwapInfoResponse {
|
||||||
"swapId": args.swap_id,
|
swap_id: args.swap_id,
|
||||||
"seller": {
|
seller: Seller {
|
||||||
"peerId": peerId.to_string(),
|
peer_id: peerId.to_string(),
|
||||||
"addresses": addresses
|
addresses,
|
||||||
},
|
},
|
||||||
"completed": is_completed,
|
completed: is_completed,
|
||||||
"startDate": start_date,
|
start_date,
|
||||||
"stateName": state_name,
|
state_name,
|
||||||
"xmrAmount": xmr_amount,
|
xmr_amount: xmr_amount.as_piconero(),
|
||||||
"btcAmount": btc_amount,
|
btc_amount,
|
||||||
"txLockId": tx_lock_id,
|
tx_lock_id,
|
||||||
"txCancelFee": tx_cancel_fee,
|
tx_cancel_fee,
|
||||||
"txRefundFee": tx_refund_fee,
|
tx_refund_fee,
|
||||||
"txLockFee": tx_lock_fee,
|
tx_lock_fee,
|
||||||
"btcRefundAddress": btc_refund_address.to_string(),
|
btc_refund_address: btc_refund_address.to_string(),
|
||||||
"cancelTimelock": cancel_timelock,
|
cancel_timelock: cancel_timelock.into(),
|
||||||
"punishTimelock": punish_timelock,
|
punish_timelock: punish_timelock.into(),
|
||||||
"timelock": timelock.map(|tl| tl.map(|tl| json!(tl)).unwrap_or(json!(null))).unwrap_or(json!(null)),
|
timelock,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn buy_xmr(buy_xmr: BuyXmrArgs, context: Arc<Context>) -> Result<serde_json::Value> {
|
async fn buy_xmr(buy_xmr: BuyXmrArgs, context: Arc<Context>) -> Result<serde_json::Value> {
|
||||||
@ -653,7 +699,7 @@ async fn cancel_and_refund(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_history(context: Arc<Context>) -> Result<serde_json::Value> {
|
async fn get_history(context: Arc<Context>) -> Result<GetHistoryResponse> {
|
||||||
let swaps = context.db.all().await?;
|
let swaps = context.db.all().await?;
|
||||||
let mut vec: Vec<(Uuid, String)> = Vec::new();
|
let mut vec: Vec<(Uuid, String)> = Vec::new();
|
||||||
for (swap_id, state) in swaps {
|
for (swap_id, state) in swaps {
|
||||||
@ -661,7 +707,7 @@ async fn get_history(context: Arc<Context>) -> Result<serde_json::Value> {
|
|||||||
vec.push((swap_id, state.to_string()));
|
vec.push((swap_id, state.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(json!({ "swaps": vec }))
|
Ok(GetHistoryResponse { swaps: vec })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_raw_states(context: Arc<Context>) -> Result<serde_json::Value> {
|
async fn get_raw_states(context: Arc<Context>) -> Result<serde_json::Value> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user