mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-05-04 15:55:04 -04:00
feat(gui): Migrate to Tauri events
- Replace Electron IPC with Tauri invoke() for API calls - Implement TauriSwapProgressEvent for state management - Remove IpcInvokeButton, replace with PromiseInvokeButton - Update models: new tauriModel.ts, refactor rpcModel.ts - Simplify SwapSlice state, remove processRunning flag - Refactor SwapStatePage to use TauriSwapProgressEvent - Update HistoryRow and HistoryRowActions for new data structures - Remove unused Electron-specific components (e.g., RpcStatusAlert) - Update dependencies: React 18, Material-UI v4 to v5 - Implement typeshare for Rust/TypeScript type synchronization - Add BobStateName enum for more precise swap state tracking - Refactor utility functions for Tauri compatibility - Remove JSONStream and other Electron-specific dependencies
This commit is contained in:
parent
d54f5c6c77
commit
cf641bc8bb
77 changed files with 2484 additions and 2167 deletions
|
@ -18,396 +18,3 @@ export interface CliLog {
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCliLog(log: unknown): log is CliLog {
|
|
||||||
if (log && typeof log === "object") {
|
|
||||||
return (
|
|
||||||
"timestamp" in (log as CliLog) &&
|
|
||||||
"level" in (log as CliLog) &&
|
|
||||||
"fields" in (log as CliLog) &&
|
|
||||||
typeof (log as CliLog).fields?.message === "string"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogStartedRpcServer extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Started RPC server";
|
|
||||||
addr: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogStartedRpcServer(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogStartedRpcServer {
|
|
||||||
return log.fields.message === "Started RPC server";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogReleasingSwapLockLog extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Releasing swap lock";
|
|
||||||
swap_id: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogReleasingSwapLockLog(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogReleasingSwapLockLog {
|
|
||||||
return log.fields.message === "Releasing swap lock";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogApiCallError extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "API call resulted in an error";
|
|
||||||
err: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogApiCallError(log: CliLog): log is CliLogApiCallError {
|
|
||||||
return log.fields.message === "API call resulted in an error";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogAcquiringSwapLockLog extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Acquiring swap lock";
|
|
||||||
swap_id: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogAcquiringSwapLockLog(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogAcquiringSwapLockLog {
|
|
||||||
return log.fields.message === "Acquiring swap lock";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogReceivedQuote extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Received quote";
|
|
||||||
price: string;
|
|
||||||
minimum_amount: string;
|
|
||||||
maximum_amount: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogReceivedQuote(log: CliLog): log is CliLogReceivedQuote {
|
|
||||||
return log.fields.message === "Received quote";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogWaitingForBtcDeposit extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Waiting for Bitcoin deposit";
|
|
||||||
deposit_address: string;
|
|
||||||
min_deposit_until_swap_will_start: string;
|
|
||||||
max_deposit_until_maximum_amount_is_reached: string;
|
|
||||||
max_giveable: string;
|
|
||||||
minimum_amount: string;
|
|
||||||
maximum_amount: string;
|
|
||||||
min_bitcoin_lock_tx_fee: string;
|
|
||||||
price: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogWaitingForBtcDeposit(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogWaitingForBtcDeposit {
|
|
||||||
return log.fields.message === "Waiting for Bitcoin deposit";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogReceivedBtc extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Received Bitcoin";
|
|
||||||
max_giveable: string;
|
|
||||||
new_balance: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogReceivedBtc(log: CliLog): log is CliLogReceivedBtc {
|
|
||||||
return log.fields.message === "Received Bitcoin";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogDeterminedSwapAmount extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Determined swap amount";
|
|
||||||
amount: string;
|
|
||||||
fees: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogDeterminedSwapAmount(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogDeterminedSwapAmount {
|
|
||||||
return log.fields.message === "Determined swap amount";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogStartedSwap extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Starting new swap";
|
|
||||||
swap_id: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogStartedSwap(log: CliLog): log is CliLogStartedSwap {
|
|
||||||
return log.fields.message === "Starting new swap";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogPublishedBtcTx extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Published Bitcoin transaction";
|
|
||||||
txid: string;
|
|
||||||
kind: "lock" | "cancel" | "withdraw" | "refund";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogPublishedBtcTx(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogPublishedBtcTx {
|
|
||||||
return log.fields.message === "Published Bitcoin transaction";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogBtcTxFound extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Found relevant Bitcoin transaction";
|
|
||||||
txid: string;
|
|
||||||
status: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogBtcTxFound(log: CliLog): log is CliLogBtcTxFound {
|
|
||||||
return log.fields.message === "Found relevant Bitcoin transaction";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogBtcTxStatusChanged extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Bitcoin transaction status changed";
|
|
||||||
txid: string;
|
|
||||||
new_status: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogBtcTxStatusChanged(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogBtcTxStatusChanged {
|
|
||||||
return log.fields.message === "Bitcoin transaction status changed";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogAliceLockedXmr extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Alice locked Monero";
|
|
||||||
txid: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogAliceLockedXmr(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogAliceLockedXmr {
|
|
||||||
return log.fields.message === "Alice locked Monero";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogReceivedXmrLockTxConfirmation extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Received new confirmation for Monero lock tx";
|
|
||||||
txid: string;
|
|
||||||
seen_confirmations: string;
|
|
||||||
needed_confirmations: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogReceivedXmrLockTxConfirmation(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogReceivedXmrLockTxConfirmation {
|
|
||||||
return log.fields.message === "Received new confirmation for Monero lock tx";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogAdvancingState extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Advancing state";
|
|
||||||
state:
|
|
||||||
| "quote has been requested"
|
|
||||||
| "execution setup done"
|
|
||||||
| "btc is locked"
|
|
||||||
| "XMR lock transaction transfer proof received"
|
|
||||||
| "xmr is locked"
|
|
||||||
| "encrypted signature is sent"
|
|
||||||
| "btc is redeemed"
|
|
||||||
| "cancel timelock is expired"
|
|
||||||
| "btc is cancelled"
|
|
||||||
| "btc is refunded"
|
|
||||||
| "xmr is redeemed"
|
|
||||||
| "btc is punished"
|
|
||||||
| "safely aborted";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogAdvancingState(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogAdvancingState {
|
|
||||||
return log.fields.message === "Advancing state";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogRedeemedXmr extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Successfully transferred XMR to wallet";
|
|
||||||
monero_receive_address: string;
|
|
||||||
txid: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogRedeemedXmr(log: CliLog): log is CliLogRedeemedXmr {
|
|
||||||
return log.fields.message === "Successfully transferred XMR to wallet";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface YouHaveBeenPunishedCliLog extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "You have been punished for not refunding in time";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isYouHaveBeenPunishedCliLog(
|
|
||||||
log: CliLog,
|
|
||||||
): log is YouHaveBeenPunishedCliLog {
|
|
||||||
return (
|
|
||||||
log.fields.message === "You have been punished for not refunding in time"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCliLogSpanAttribute<T>(log: CliLog, key: string): T | null {
|
|
||||||
const span = log.spans?.find((s) => s[key]);
|
|
||||||
if (!span) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return span[key] as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCliLogSpanSwapId(log: CliLog): string | null {
|
|
||||||
return getCliLogSpanAttribute<string>(log, "swap_id");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCliLogSpanLogReferenceId(log: CliLog): string | null {
|
|
||||||
return (
|
|
||||||
getCliLogSpanAttribute<string>(log, "log_reference_id")?.replace(
|
|
||||||
/"/g,
|
|
||||||
"",
|
|
||||||
) || null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasCliLogOneOfMultipleSpans(
|
|
||||||
log: CliLog,
|
|
||||||
spanNames: string[],
|
|
||||||
): boolean {
|
|
||||||
return log.spans?.some((s) => spanNames.includes(s.name)) ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogStartedSyncingMoneroWallet extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Syncing Monero wallet";
|
|
||||||
current_sync_height?: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogStartedSyncingMoneroWallet(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogStartedSyncingMoneroWallet {
|
|
||||||
return log.fields.message === "Syncing Monero wallet";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogFinishedSyncingMoneroWallet extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Synced Monero wallet";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogFailedToSyncMoneroWallet extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Failed to sync Monero wallet";
|
|
||||||
error: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogFailedToSyncMoneroWallet(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogFailedToSyncMoneroWallet {
|
|
||||||
return log.fields.message === "Failed to sync Monero wallet";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogFinishedSyncingMoneroWallet(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogFinishedSyncingMoneroWallet {
|
|
||||||
return log.fields.message === "Monero wallet synced";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Downloading monero-wallet-rpc";
|
|
||||||
progress: string;
|
|
||||||
size: string;
|
|
||||||
download_url: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogDownloadingMoneroWalletRpc(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogDownloadingMoneroWalletRpc {
|
|
||||||
return log.fields.message === "Downloading monero-wallet-rpc";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogStartedSyncingMoneroWallet extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Syncing Monero wallet";
|
|
||||||
current_sync_height?: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogDownloadingMoneroWalletRpc extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Downloading monero-wallet-rpc";
|
|
||||||
progress: string;
|
|
||||||
size: string;
|
|
||||||
download_url: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogGotNotificationForNewBlock extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Got notification for new block";
|
|
||||||
block_height: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogGotNotificationForNewBlock(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogGotNotificationForNewBlock {
|
|
||||||
return log.fields.message === "Got notification for new block";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogAttemptingToCooperativelyRedeemXmr extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Attempting to cooperatively redeem XMR after being punished";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogAttemptingToCooperativelyRedeemXmr(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogAttemptingToCooperativelyRedeemXmr {
|
|
||||||
return (
|
|
||||||
log.fields.message ===
|
|
||||||
"Attempting to cooperatively redeem XMR after being punished"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr
|
|
||||||
extends CliLog {
|
|
||||||
fields: {
|
|
||||||
message: "Alice has accepted our request to cooperatively redeem the XMR";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr(
|
|
||||||
log: CliLog,
|
|
||||||
): log is CliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr {
|
|
||||||
return (
|
|
||||||
log.fields.message ===
|
|
||||||
"Alice has accepted our request to cooperatively redeem the XMR"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
export interface Binary {
|
|
||||||
dirPath: string; // Path without filename appended
|
|
||||||
fileName: string;
|
|
||||||
}
|
|
|
@ -1,6 +1,3 @@
|
||||||
import { piconerosToXmr, satsToBtc } from "utils/conversionUtils";
|
|
||||||
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",
|
||||||
|
@ -110,227 +107,9 @@ export type SwapSellerInfo = {
|
||||||
addresses: string[];
|
addresses: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface GetSwapInfoResponse {
|
|
||||||
swap_id: string;
|
|
||||||
completed: boolean;
|
|
||||||
seller: SwapSellerInfo;
|
|
||||||
start_date: string;
|
|
||||||
state_name: SwapStateName;
|
|
||||||
timelock: null | SwapTimelockInfo;
|
|
||||||
tx_lock_id: string;
|
|
||||||
tx_cancel_fee: number;
|
|
||||||
tx_refund_fee: number;
|
|
||||||
tx_lock_fee: number;
|
|
||||||
btc_amount: number;
|
|
||||||
xmr_amount: number;
|
|
||||||
btc_refund_address: string;
|
|
||||||
cancel_timelock: number;
|
|
||||||
punish_timelock: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MoneroRecoveryResponse = {
|
export type MoneroRecoveryResponse = {
|
||||||
address: string;
|
address: string;
|
||||||
spend_key: string;
|
spend_key: string;
|
||||||
view_key: string;
|
view_key: string;
|
||||||
restore_height: number;
|
restore_height: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface BalanceBitcoinResponse {
|
|
||||||
balance: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetHistoryResponse {
|
|
||||||
swaps: [swapId: string, stateName: SwapStateName][];
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SwapStateName {
|
|
||||||
Started = "quote has been requested",
|
|
||||||
SwapSetupCompleted = "execution setup done",
|
|
||||||
BtcLocked = "btc is locked",
|
|
||||||
XmrLockProofReceived = "XMR lock transaction transfer proof received",
|
|
||||||
XmrLocked = "xmr is locked",
|
|
||||||
EncSigSent = "encrypted signature is sent",
|
|
||||||
BtcRedeemed = "btc is redeemed",
|
|
||||||
CancelTimelockExpired = "cancel timelock is expired",
|
|
||||||
BtcCancelled = "btc is cancelled",
|
|
||||||
BtcRefunded = "btc is refunded",
|
|
||||||
XmrRedeemed = "xmr is redeemed",
|
|
||||||
BtcPunished = "btc is punished",
|
|
||||||
SafelyAborted = "safely aborted",
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SwapStateNameRunningSwap = Exclude<
|
|
||||||
SwapStateName,
|
|
||||||
| SwapStateName.Started
|
|
||||||
| SwapStateName.SwapSetupCompleted
|
|
||||||
| SwapStateName.BtcRefunded
|
|
||||||
| SwapStateName.BtcPunished
|
|
||||||
| SwapStateName.SafelyAborted
|
|
||||||
| SwapStateName.XmrRedeemed
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type GetSwapInfoResponseRunningSwap = GetSwapInfoResponse & {
|
|
||||||
stateName: SwapStateNameRunningSwap;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function isSwapStateNameRunningSwap(
|
|
||||||
state: SwapStateName,
|
|
||||||
): state is SwapStateNameRunningSwap {
|
|
||||||
return ![
|
|
||||||
SwapStateName.Started,
|
|
||||||
SwapStateName.SwapSetupCompleted,
|
|
||||||
SwapStateName.BtcRefunded,
|
|
||||||
SwapStateName.BtcPunished,
|
|
||||||
SwapStateName.SafelyAborted,
|
|
||||||
SwapStateName.XmrRedeemed,
|
|
||||||
].includes(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SwapStateNameCompletedSwap =
|
|
||||||
| SwapStateName.XmrRedeemed
|
|
||||||
| SwapStateName.BtcRefunded
|
|
||||||
| SwapStateName.BtcPunished
|
|
||||||
| SwapStateName.SafelyAborted;
|
|
||||||
|
|
||||||
export function isSwapStateNameCompletedSwap(
|
|
||||||
state: SwapStateName,
|
|
||||||
): state is SwapStateNameCompletedSwap {
|
|
||||||
return [
|
|
||||||
SwapStateName.XmrRedeemed,
|
|
||||||
SwapStateName.BtcRefunded,
|
|
||||||
SwapStateName.BtcPunished,
|
|
||||||
SwapStateName.SafelyAborted,
|
|
||||||
].includes(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SwapStateNamePossiblyCancellableSwap =
|
|
||||||
| SwapStateName.BtcLocked
|
|
||||||
| SwapStateName.XmrLockProofReceived
|
|
||||||
| SwapStateName.XmrLocked
|
|
||||||
| SwapStateName.EncSigSent
|
|
||||||
| SwapStateName.CancelTimelockExpired;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Checks if a swap is in a state where it can possibly be cancelled
|
|
||||||
|
|
||||||
The following conditions must be met:
|
|
||||||
- The bitcoin must be locked
|
|
||||||
- The bitcoin must not be redeemed
|
|
||||||
- The bitcoin must not be cancelled
|
|
||||||
- The bitcoin must not be refunded
|
|
||||||
- The bitcoin must not be punished
|
|
||||||
|
|
||||||
See: https://github.com/comit-network/xmr-btc-swap/blob/7023e75bb51ab26dff4c8fcccdc855d781ca4b15/swap/src/cli/cancel.rs#L16-L35
|
|
||||||
*/
|
|
||||||
export function isSwapStateNamePossiblyCancellableSwap(
|
|
||||||
state: SwapStateName,
|
|
||||||
): state is SwapStateNamePossiblyCancellableSwap {
|
|
||||||
return [
|
|
||||||
SwapStateName.BtcLocked,
|
|
||||||
SwapStateName.XmrLockProofReceived,
|
|
||||||
SwapStateName.XmrLocked,
|
|
||||||
SwapStateName.EncSigSent,
|
|
||||||
SwapStateName.CancelTimelockExpired,
|
|
||||||
].includes(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SwapStateNamePossiblyRefundableSwap =
|
|
||||||
| SwapStateName.BtcLocked
|
|
||||||
| SwapStateName.XmrLockProofReceived
|
|
||||||
| SwapStateName.XmrLocked
|
|
||||||
| SwapStateName.EncSigSent
|
|
||||||
| SwapStateName.CancelTimelockExpired
|
|
||||||
| SwapStateName.BtcCancelled;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Checks if a swap is in a state where it can possibly be refunded (meaning it's not impossible)
|
|
||||||
|
|
||||||
The following conditions must be met:
|
|
||||||
- The bitcoin must be locked
|
|
||||||
- The bitcoin must not be redeemed
|
|
||||||
- The bitcoin must not be refunded
|
|
||||||
- The bitcoin must not be punished
|
|
||||||
|
|
||||||
See: https://github.com/comit-network/xmr-btc-swap/blob/7023e75bb51ab26dff4c8fcccdc855d781ca4b15/swap/src/cli/refund.rs#L16-L34
|
|
||||||
*/
|
|
||||||
export function isSwapStateNamePossiblyRefundableSwap(
|
|
||||||
state: SwapStateName,
|
|
||||||
): state is SwapStateNamePossiblyRefundableSwap {
|
|
||||||
return [
|
|
||||||
SwapStateName.BtcLocked,
|
|
||||||
SwapStateName.XmrLockProofReceived,
|
|
||||||
SwapStateName.XmrLocked,
|
|
||||||
SwapStateName.EncSigSent,
|
|
||||||
SwapStateName.CancelTimelockExpired,
|
|
||||||
SwapStateName.BtcCancelled,
|
|
||||||
].includes(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type guard for GetSwapInfoResponseRunningSwap
|
|
||||||
* "running" means the swap is in progress and not yet completed
|
|
||||||
* If a swap is not "running" it means it is either completed or no Bitcoin have been locked yet
|
|
||||||
* @param response
|
|
||||||
*/
|
|
||||||
export function isGetSwapInfoResponseRunningSwap(
|
|
||||||
response: GetSwapInfoResponse,
|
|
||||||
): response is GetSwapInfoResponseRunningSwap {
|
|
||||||
return isSwapStateNameRunningSwap(response.state_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapMoneroRecoverable(swapStateName: SwapStateName): boolean {
|
|
||||||
return [SwapStateName.BtcRedeemed].includes(swapStateName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://github.com/comit-network/xmr-btc-swap/blob/50ae54141255e03dba3d2b09036b1caa4a63e5a3/swap/src/protocol/bob/state.rs#L55
|
|
||||||
export function getHumanReadableDbStateType(type: SwapStateName): string {
|
|
||||||
switch (type) {
|
|
||||||
case SwapStateName.Started:
|
|
||||||
return "Quote has been requested";
|
|
||||||
case SwapStateName.SwapSetupCompleted:
|
|
||||||
return "Swap has been initiated";
|
|
||||||
case SwapStateName.BtcLocked:
|
|
||||||
return "Bitcoin has been locked";
|
|
||||||
case SwapStateName.XmrLockProofReceived:
|
|
||||||
return "Monero lock transaction transfer proof has been received";
|
|
||||||
case SwapStateName.XmrLocked:
|
|
||||||
return "Monero has been locked";
|
|
||||||
case SwapStateName.EncSigSent:
|
|
||||||
return "Encrypted signature has been sent";
|
|
||||||
case SwapStateName.BtcRedeemed:
|
|
||||||
return "Bitcoin has been redeemed";
|
|
||||||
case SwapStateName.CancelTimelockExpired:
|
|
||||||
return "Cancel timelock has expired";
|
|
||||||
case SwapStateName.BtcCancelled:
|
|
||||||
return "Swap has been cancelled";
|
|
||||||
case SwapStateName.BtcRefunded:
|
|
||||||
return "Bitcoin has been refunded";
|
|
||||||
case SwapStateName.XmrRedeemed:
|
|
||||||
return "Monero has been redeemed";
|
|
||||||
case SwapStateName.BtcPunished:
|
|
||||||
return "Bitcoin has been punished";
|
|
||||||
case SwapStateName.SafelyAborted:
|
|
||||||
return "Swap has been safely aborted";
|
|
||||||
default:
|
|
||||||
return exhaustiveGuard(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSwapTxFees(swap: GetSwapInfoResponse): number {
|
|
||||||
return satsToBtc(swap.tx_lock_fee);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSwapBtcAmount(swap: GetSwapInfoResponse): number {
|
|
||||||
return satsToBtc(swap.btc_amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSwapXmrAmount(swap: GetSwapInfoResponse): number {
|
|
||||||
return piconerosToXmr(swap.xmr_amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSwapExchangeRate(swap: GetSwapInfoResponse): number {
|
|
||||||
const btcAmount = getSwapBtcAmount(swap);
|
|
||||||
const xmrAmount = getSwapXmrAmount(swap);
|
|
||||||
|
|
||||||
return btcAmount / xmrAmount;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,218 +1,12 @@
|
||||||
import { CliLog, SwapSpawnType } from "./cliModel";
|
import { CliLog, SwapSpawnType } from "./cliModel";
|
||||||
import { Provider } from "./apiModel";
|
import { TauriSwapProgressEvent } from "./tauriModel";
|
||||||
|
|
||||||
export interface SwapSlice {
|
export interface SwapSlice {
|
||||||
state: SwapState | null;
|
state: {
|
||||||
logs: CliLog[];
|
curr: TauriSwapProgressEvent;
|
||||||
processRunning: boolean;
|
prev: TauriSwapProgressEvent | null;
|
||||||
provider: Provider | null;
|
swapId: string;
|
||||||
spawnType: SwapSpawnType | null;
|
|
||||||
swapId: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MoneroWalletRpcUpdateState = {
|
|
||||||
progress: string;
|
|
||||||
downloadUrl: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface SwapState {
|
|
||||||
type: SwapStateType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SwapStateType {
|
|
||||||
INITIATED = "initiated",
|
|
||||||
RECEIVED_QUOTE = "received quote",
|
|
||||||
WAITING_FOR_BTC_DEPOSIT = "waiting for btc deposit",
|
|
||||||
STARTED = "started",
|
|
||||||
BTC_LOCK_TX_IN_MEMPOOL = "btc lock tx is in mempool",
|
|
||||||
XMR_LOCK_TX_IN_MEMPOOL = "xmr lock tx is in mempool",
|
|
||||||
XMR_LOCKED = "xmr is locked",
|
|
||||||
BTC_REDEEMED = "btc redeemed",
|
|
||||||
XMR_REDEEM_IN_MEMPOOL = "xmr redeem tx is in mempool",
|
|
||||||
PROCESS_EXITED = "process exited",
|
|
||||||
BTC_CANCELLED = "btc cancelled",
|
|
||||||
BTC_REFUNDED = "btc refunded",
|
|
||||||
BTC_PUNISHED = "btc punished",
|
|
||||||
ATTEMPTING_COOPERATIVE_REDEEM = "attempting cooperative redeem",
|
|
||||||
COOPERATIVE_REDEEM_REJECTED = "cooperative redeem rejected",
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapState(state?: SwapState | null): state is SwapState {
|
|
||||||
return state?.type != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateInitiated extends SwapState {
|
|
||||||
type: SwapStateType.INITIATED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateInitiated(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateInitiated {
|
|
||||||
return state?.type === SwapStateType.INITIATED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateReceivedQuote extends SwapState {
|
|
||||||
type: SwapStateType.RECEIVED_QUOTE;
|
|
||||||
price: number;
|
|
||||||
minimumSwapAmount: number;
|
|
||||||
maximumSwapAmount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateReceivedQuote(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateReceivedQuote {
|
|
||||||
return state?.type === SwapStateType.RECEIVED_QUOTE;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateWaitingForBtcDeposit extends SwapState {
|
|
||||||
type: SwapStateType.WAITING_FOR_BTC_DEPOSIT;
|
|
||||||
depositAddress: string;
|
|
||||||
maxGiveable: number;
|
|
||||||
minimumAmount: number;
|
|
||||||
maximumAmount: number;
|
|
||||||
minDeposit: number;
|
|
||||||
maxDeposit: number;
|
|
||||||
minBitcoinLockTxFee: number;
|
|
||||||
price: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateWaitingForBtcDeposit(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateWaitingForBtcDeposit {
|
|
||||||
return state?.type === SwapStateType.WAITING_FOR_BTC_DEPOSIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateStarted extends SwapState {
|
|
||||||
type: SwapStateType.STARTED;
|
|
||||||
txLockDetails: {
|
|
||||||
amount: number;
|
|
||||||
fees: number;
|
|
||||||
} | null;
|
} | null;
|
||||||
}
|
logs: CliLog[];
|
||||||
|
spawnType: SwapSpawnType | null;
|
||||||
export function isSwapStateStarted(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateStarted {
|
|
||||||
return state?.type === SwapStateType.STARTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateBtcLockInMempool extends SwapState {
|
|
||||||
type: SwapStateType.BTC_LOCK_TX_IN_MEMPOOL;
|
|
||||||
bobBtcLockTxId: string;
|
|
||||||
bobBtcLockTxConfirmations: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateBtcLockInMempool(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateBtcLockInMempool {
|
|
||||||
return state?.type === SwapStateType.BTC_LOCK_TX_IN_MEMPOOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateXmrLockInMempool extends SwapState {
|
|
||||||
type: SwapStateType.XMR_LOCK_TX_IN_MEMPOOL;
|
|
||||||
aliceXmrLockTxId: string;
|
|
||||||
aliceXmrLockTxConfirmations: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateXmrLockInMempool(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateXmrLockInMempool {
|
|
||||||
return state?.type === SwapStateType.XMR_LOCK_TX_IN_MEMPOOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateXmrLocked extends SwapState {
|
|
||||||
type: SwapStateType.XMR_LOCKED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateXmrLocked(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateXmrLocked {
|
|
||||||
return state?.type === SwapStateType.XMR_LOCKED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateBtcRedemeed extends SwapState {
|
|
||||||
type: SwapStateType.BTC_REDEEMED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateBtcRedemeed(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateBtcRedemeed {
|
|
||||||
return state?.type === SwapStateType.BTC_REDEEMED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateAttemptingCooperativeRedeeem extends SwapState {
|
|
||||||
type: SwapStateType.ATTEMPTING_COOPERATIVE_REDEEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateAttemptingCooperativeRedeeem(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateAttemptingCooperativeRedeeem {
|
|
||||||
return state?.type === SwapStateType.ATTEMPTING_COOPERATIVE_REDEEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateCooperativeRedeemRejected extends SwapState {
|
|
||||||
type: SwapStateType.COOPERATIVE_REDEEM_REJECTED;
|
|
||||||
reason: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateCooperativeRedeemRejected(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateCooperativeRedeemRejected {
|
|
||||||
return state?.type === SwapStateType.COOPERATIVE_REDEEM_REJECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateXmrRedeemInMempool extends SwapState {
|
|
||||||
type: SwapStateType.XMR_REDEEM_IN_MEMPOOL;
|
|
||||||
bobXmrRedeemTxId: string;
|
|
||||||
bobXmrRedeemAddress: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateXmrRedeemInMempool(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateXmrRedeemInMempool {
|
|
||||||
return state?.type === SwapStateType.XMR_REDEEM_IN_MEMPOOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateBtcCancelled extends SwapState {
|
|
||||||
type: SwapStateType.BTC_CANCELLED;
|
|
||||||
btcCancelTxId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateBtcCancelled(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateBtcCancelled {
|
|
||||||
return state?.type === SwapStateType.BTC_CANCELLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateBtcRefunded extends SwapState {
|
|
||||||
type: SwapStateType.BTC_REFUNDED;
|
|
||||||
bobBtcRefundTxId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateBtcRefunded(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateBtcRefunded {
|
|
||||||
return state?.type === SwapStateType.BTC_REFUNDED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateBtcPunished extends SwapState {
|
|
||||||
type: SwapStateType.BTC_PUNISHED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateBtcPunished(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateBtcPunished {
|
|
||||||
return state?.type === SwapStateType.BTC_PUNISHED;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwapStateProcessExited extends SwapState {
|
|
||||||
type: SwapStateType.PROCESS_EXITED;
|
|
||||||
prevState: SwapState | null;
|
|
||||||
rpcError: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSwapStateProcessExited(
|
|
||||||
state?: SwapState | null,
|
|
||||||
): state is SwapStateProcessExited {
|
|
||||||
return state?.type === SwapStateType.PROCESS_EXITED;
|
|
||||||
}
|
}
|
||||||
|
|
155
src-gui/src/models/tauriModelExt.ts
Normal file
155
src-gui/src/models/tauriModelExt.ts
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
import {
|
||||||
|
ExpiredTimelocks,
|
||||||
|
GetSwapInfoResponse,
|
||||||
|
TauriSwapProgressEvent,
|
||||||
|
} from "./tauriModel";
|
||||||
|
|
||||||
|
export type TauriSwapProgressEventContent<
|
||||||
|
T extends TauriSwapProgressEvent["type"],
|
||||||
|
> = Extract<TauriSwapProgressEvent, { type: T }>["content"];
|
||||||
|
|
||||||
|
// See /swap/src/protocol/bob/state.rs#L57
|
||||||
|
// TODO: Replace this with a typeshare definition
|
||||||
|
export enum BobStateName {
|
||||||
|
Started = "quote has been requested",
|
||||||
|
SwapSetupCompleted = "execution setup done",
|
||||||
|
BtcLocked = "btc is locked",
|
||||||
|
XmrLockProofReceived = "XMR lock transaction transfer proof received",
|
||||||
|
XmrLocked = "xmr is locked",
|
||||||
|
EncSigSent = "encrypted signature is sent",
|
||||||
|
BtcRedeemed = "btc is redeemed",
|
||||||
|
CancelTimelockExpired = "cancel timelock is expired",
|
||||||
|
BtcCancelled = "btc is cancelled",
|
||||||
|
BtcRefunded = "btc is refunded",
|
||||||
|
XmrRedeemed = "xmr is redeemed",
|
||||||
|
BtcPunished = "btc is punished",
|
||||||
|
SafelyAborted = "safely aborted",
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This is a temporary solution until we have a typeshare definition for BobStateName
|
||||||
|
export type GetSwapInfoResponseExt = GetSwapInfoResponse & {
|
||||||
|
state_name: BobStateName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TimelockNone = Extract<ExpiredTimelocks, { type: "None" }>;
|
||||||
|
export type TimelockCancel = Extract<ExpiredTimelocks, { type: "Cancel" }>;
|
||||||
|
export type TimelockPunish = Extract<ExpiredTimelocks, { type: "Punish" }>;
|
||||||
|
|
||||||
|
export type BobStateNameRunningSwap = Exclude<
|
||||||
|
BobStateName,
|
||||||
|
| BobStateName.Started
|
||||||
|
| BobStateName.SwapSetupCompleted
|
||||||
|
| BobStateName.BtcRefunded
|
||||||
|
| BobStateName.BtcPunished
|
||||||
|
| BobStateName.SafelyAborted
|
||||||
|
| BobStateName.XmrRedeemed
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type GetSwapInfoResponseExtRunningSwap = GetSwapInfoResponseExt & {
|
||||||
|
stateName: BobStateNameRunningSwap;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isBobStateNameRunningSwap(
|
||||||
|
state: BobStateName,
|
||||||
|
): state is BobStateNameRunningSwap {
|
||||||
|
return ![
|
||||||
|
BobStateName.Started,
|
||||||
|
BobStateName.SwapSetupCompleted,
|
||||||
|
BobStateName.BtcRefunded,
|
||||||
|
BobStateName.BtcPunished,
|
||||||
|
BobStateName.SafelyAborted,
|
||||||
|
BobStateName.XmrRedeemed,
|
||||||
|
].includes(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BobStateNameCompletedSwap =
|
||||||
|
| BobStateName.XmrRedeemed
|
||||||
|
| BobStateName.BtcRefunded
|
||||||
|
| BobStateName.BtcPunished
|
||||||
|
| BobStateName.SafelyAborted;
|
||||||
|
|
||||||
|
export function isBobStateNameCompletedSwap(
|
||||||
|
state: BobStateName,
|
||||||
|
): state is BobStateNameCompletedSwap {
|
||||||
|
return [
|
||||||
|
BobStateName.XmrRedeemed,
|
||||||
|
BobStateName.BtcRefunded,
|
||||||
|
BobStateName.BtcPunished,
|
||||||
|
BobStateName.SafelyAborted,
|
||||||
|
].includes(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BobStateNamePossiblyCancellableSwap =
|
||||||
|
| BobStateName.BtcLocked
|
||||||
|
| BobStateName.XmrLockProofReceived
|
||||||
|
| BobStateName.XmrLocked
|
||||||
|
| BobStateName.EncSigSent
|
||||||
|
| BobStateName.CancelTimelockExpired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if a swap is in a state where it can possibly be cancelled
|
||||||
|
|
||||||
|
The following conditions must be met:
|
||||||
|
- The bitcoin must be locked
|
||||||
|
- The bitcoin must not be redeemed
|
||||||
|
- The bitcoin must not be cancelled
|
||||||
|
- The bitcoin must not be refunded
|
||||||
|
- The bitcoin must not be punished
|
||||||
|
|
||||||
|
See: https://github.com/comit-network/xmr-btc-swap/blob/7023e75bb51ab26dff4c8fcccdc855d781ca4b15/swap/src/cli/cancel.rs#L16-L35
|
||||||
|
*/
|
||||||
|
export function isBobStateNamePossiblyCancellableSwap(
|
||||||
|
state: BobStateName,
|
||||||
|
): state is BobStateNamePossiblyCancellableSwap {
|
||||||
|
return [
|
||||||
|
BobStateName.BtcLocked,
|
||||||
|
BobStateName.XmrLockProofReceived,
|
||||||
|
BobStateName.XmrLocked,
|
||||||
|
BobStateName.EncSigSent,
|
||||||
|
BobStateName.CancelTimelockExpired,
|
||||||
|
].includes(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BobStateNamePossiblyRefundableSwap =
|
||||||
|
| BobStateName.BtcLocked
|
||||||
|
| BobStateName.XmrLockProofReceived
|
||||||
|
| BobStateName.XmrLocked
|
||||||
|
| BobStateName.EncSigSent
|
||||||
|
| BobStateName.CancelTimelockExpired
|
||||||
|
| BobStateName.BtcCancelled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if a swap is in a state where it can possibly be refunded (meaning it's not impossible)
|
||||||
|
|
||||||
|
The following conditions must be met:
|
||||||
|
- The bitcoin must be locked
|
||||||
|
- The bitcoin must not be redeemed
|
||||||
|
- The bitcoin must not be refunded
|
||||||
|
- The bitcoin must not be punished
|
||||||
|
|
||||||
|
See: https://github.com/comit-network/xmr-btc-swap/blob/7023e75bb51ab26dff4c8fcccdc855d781ca4b15/swap/src/cli/refund.rs#L16-L34
|
||||||
|
*/
|
||||||
|
export function isBobStateNamePossiblyRefundableSwap(
|
||||||
|
state: BobStateName,
|
||||||
|
): state is BobStateNamePossiblyRefundableSwap {
|
||||||
|
return [
|
||||||
|
BobStateName.BtcLocked,
|
||||||
|
BobStateName.XmrLockProofReceived,
|
||||||
|
BobStateName.XmrLocked,
|
||||||
|
BobStateName.EncSigSent,
|
||||||
|
BobStateName.CancelTimelockExpired,
|
||||||
|
BobStateName.BtcCancelled,
|
||||||
|
].includes(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for GetSwapInfoResponseExt
|
||||||
|
* "running" means the swap is in progress and not yet completed
|
||||||
|
* If a swap is not "running" it means it is either completed or no Bitcoin have been locked yet
|
||||||
|
* @param response
|
||||||
|
*/
|
||||||
|
export function isGetSwapInfoResponseRunningSwap(
|
||||||
|
response: GetSwapInfoResponseExt,
|
||||||
|
): response is GetSwapInfoResponseExtRunningSwap {
|
||||||
|
return isBobStateNameRunningSwap(response.state_name);
|
||||||
|
}
|
|
@ -1,166 +0,0 @@
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
ButtonProps,
|
|
||||||
CircularProgress,
|
|
||||||
IconButton,
|
|
||||||
Tooltip,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import { ReactElement, ReactNode, useEffect, useState } from "react";
|
|
||||||
import { useSnackbar } from "notistack";
|
|
||||||
import { useAppSelector } from "store/hooks";
|
|
||||||
import { RpcProcessStateType } from "models/rpcModel";
|
|
||||||
import { isExternalRpc } from "store/config";
|
|
||||||
|
|
||||||
function IpcButtonTooltip({
|
|
||||||
requiresRpcAndNotReady,
|
|
||||||
children,
|
|
||||||
processType,
|
|
||||||
tooltipTitle,
|
|
||||||
}: {
|
|
||||||
requiresRpcAndNotReady: boolean;
|
|
||||||
children: ReactElement;
|
|
||||||
processType: RpcProcessStateType;
|
|
||||||
tooltipTitle?: string;
|
|
||||||
}) {
|
|
||||||
if (tooltipTitle) {
|
|
||||||
return <Tooltip title={tooltipTitle}>{children}</Tooltip>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMessage = () => {
|
|
||||||
if (!requiresRpcAndNotReady) return "";
|
|
||||||
|
|
||||||
switch (processType) {
|
|
||||||
case RpcProcessStateType.LISTENING_FOR_CONNECTIONS:
|
|
||||||
return "";
|
|
||||||
case RpcProcessStateType.STARTED:
|
|
||||||
return "Cannot execute this action because the Swap Daemon is still starting and not yet ready to accept connections. Please wait a moment and try again";
|
|
||||||
case RpcProcessStateType.EXITED:
|
|
||||||
return "Cannot execute this action because the Swap Daemon has been stopped. Please start the Swap Daemon again to continue";
|
|
||||||
case RpcProcessStateType.NOT_STARTED:
|
|
||||||
return "Cannot execute this action because the Swap Daemon has not been started yet. Please start the Swap Daemon first";
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip title={getMessage()} color="red">
|
|
||||||
{children}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IpcInvokeButtonProps<T> {
|
|
||||||
ipcArgs: unknown[];
|
|
||||||
ipcChannel: string;
|
|
||||||
onSuccess?: (data: T) => void;
|
|
||||||
isLoadingOverride?: boolean;
|
|
||||||
isIconButton?: boolean;
|
|
||||||
loadIcon?: ReactNode;
|
|
||||||
requiresRpc?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
displayErrorSnackbar?: boolean;
|
|
||||||
tooltipTitle?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DELAY_BEFORE_SHOWING_LOADING_MS = 0;
|
|
||||||
|
|
||||||
export default function IpcInvokeButton<T>({
|
|
||||||
disabled,
|
|
||||||
ipcChannel,
|
|
||||||
ipcArgs,
|
|
||||||
onSuccess,
|
|
||||||
onClick,
|
|
||||||
endIcon,
|
|
||||||
loadIcon,
|
|
||||||
isLoadingOverride,
|
|
||||||
isIconButton,
|
|
||||||
requiresRpc,
|
|
||||||
displayErrorSnackbar,
|
|
||||||
tooltipTitle,
|
|
||||||
...rest
|
|
||||||
}: IpcInvokeButtonProps<T> & ButtonProps) {
|
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
|
||||||
|
|
||||||
const rpcProcessType = useAppSelector((state) => state.rpc.process.type);
|
|
||||||
const isRpcReady =
|
|
||||||
rpcProcessType === RpcProcessStateType.LISTENING_FOR_CONNECTIONS;
|
|
||||||
const [isPending, setIsPending] = useState(false);
|
|
||||||
const [hasMinLoadingTimePassed, setHasMinLoadingTimePassed] = useState(false);
|
|
||||||
|
|
||||||
const isLoading = (isPending && hasMinLoadingTimePassed) || isLoadingOverride;
|
|
||||||
const actualEndIcon = isLoading
|
|
||||||
? loadIcon || <CircularProgress size="1rem" />
|
|
||||||
: endIcon;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setHasMinLoadingTimePassed(false);
|
|
||||||
setTimeout(
|
|
||||||
() => setHasMinLoadingTimePassed(true),
|
|
||||||
DELAY_BEFORE_SHOWING_LOADING_MS,
|
|
||||||
);
|
|
||||||
}, [isPending]);
|
|
||||||
|
|
||||||
async function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
|
|
||||||
onClick?.(event);
|
|
||||||
|
|
||||||
if (!isPending) {
|
|
||||||
setIsPending(true);
|
|
||||||
try {
|
|
||||||
// const result = await ipcRenderer.invoke(ipcChannel, ...ipcArgs);
|
|
||||||
throw new Error("Not implemented");
|
|
||||||
// onSuccess?.(result);
|
|
||||||
} catch (e: unknown) {
|
|
||||||
if (displayErrorSnackbar) {
|
|
||||||
enqueueSnackbar((e as Error).message, {
|
|
||||||
autoHideDuration: 60 * 1000,
|
|
||||||
variant: "error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setIsPending(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const requiresRpcAndNotReady =
|
|
||||||
!!requiresRpc && !isRpcReady && !isExternalRpc();
|
|
||||||
const isDisabled = disabled || requiresRpcAndNotReady || isLoading;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IpcButtonTooltip
|
|
||||||
requiresRpcAndNotReady={requiresRpcAndNotReady}
|
|
||||||
processType={rpcProcessType}
|
|
||||||
tooltipTitle={tooltipTitle}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{isIconButton ? (
|
|
||||||
<IconButton
|
|
||||||
onClick={handleClick}
|
|
||||||
disabled={isDisabled}
|
|
||||||
{...(rest as any)}
|
|
||||||
>
|
|
||||||
{actualEndIcon}
|
|
||||||
</IconButton>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
onClick={handleClick}
|
|
||||||
disabled={isDisabled}
|
|
||||||
endIcon={actualEndIcon}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</IpcButtonTooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
IpcInvokeButton.defaultProps = {
|
|
||||||
requiresRpc: true,
|
|
||||||
disabled: false,
|
|
||||||
onSuccess: undefined,
|
|
||||||
isLoadingOverride: false,
|
|
||||||
isIconButton: false,
|
|
||||||
loadIcon: undefined,
|
|
||||||
displayErrorSnackbar: true,
|
|
||||||
};
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Button, ButtonProps, IconButton, Tooltip } from "@material-ui/core";
|
import { Button, ButtonProps, IconButton } from "@material-ui/core";
|
||||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from "notistack";
|
||||||
import { ReactNode, useEffect, useState } from "react";
|
import { ReactNode, useState } from "react";
|
||||||
|
|
||||||
interface IpcInvokeButtonProps<T> {
|
interface PromiseInvokeButtonProps<T> {
|
||||||
onSuccess?: (data: T) => void;
|
onSuccess?: (data: T) => void;
|
||||||
onClick: () => Promise<T>;
|
onClick: () => Promise<T>;
|
||||||
onPendingChange?: (isPending: boolean) => void;
|
onPendingChange?: (isPending: boolean) => void;
|
||||||
|
@ -24,10 +24,9 @@ export default function PromiseInvokeButton<T>({
|
||||||
isLoadingOverride,
|
isLoadingOverride,
|
||||||
isIconButton,
|
isIconButton,
|
||||||
displayErrorSnackbar,
|
displayErrorSnackbar,
|
||||||
tooltipTitle,
|
|
||||||
onPendingChange,
|
onPendingChange,
|
||||||
...rest
|
...rest
|
||||||
}: IpcInvokeButtonProps<T> & ButtonProps) {
|
}: ButtonProps & PromiseInvokeButtonProps<T>) {
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
@ -42,11 +41,11 @@ export default function PromiseInvokeButton<T>({
|
||||||
try {
|
try {
|
||||||
onPendingChange?.(true);
|
onPendingChange?.(true);
|
||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
let result = await onClick();
|
const result = await onClick();
|
||||||
onSuccess?.(result);
|
onSuccess?.(result);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (displayErrorSnackbar) {
|
if (displayErrorSnackbar) {
|
||||||
enqueueSnackbar(e as String, {
|
enqueueSnackbar(e as string, {
|
||||||
autoHideDuration: 60 * 1000,
|
autoHideDuration: 60 * 1000,
|
||||||
variant: "error",
|
variant: "error",
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { Alert } from "@material-ui/lab";
|
|
||||||
import { Box, LinearProgress } from "@material-ui/core";
|
import { Box, LinearProgress } from "@material-ui/core";
|
||||||
|
import { Alert } from "@material-ui/lab";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
|
|
||||||
export default function MoneroWalletRpcUpdatingAlert() {
|
export default function MoneroWalletRpcUpdatingAlert() {
|
||||||
|
// TODO: Reimplement this using Tauri Events
|
||||||
|
return <></>;
|
||||||
|
|
||||||
const updateState = useAppSelector(
|
const updateState = useAppSelector(
|
||||||
(s) => s.rpc.state.moneroWalletRpc.updateState,
|
(s) => s.rpc.state.moneroWalletRpc.updateState,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Alert } from "@material-ui/lab";
|
|
||||||
import { Box, makeStyles } from "@material-ui/core";
|
import { Box, makeStyles } from "@material-ui/core";
|
||||||
|
import { Alert } from "@material-ui/lab";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
import WalletRefreshButton from "../pages/wallet/WalletRefreshButton";
|
|
||||||
import { SatsAmount } from "../other/Units";
|
import { SatsAmount } from "../other/Units";
|
||||||
|
import WalletRefreshButton from "../pages/wallet/WalletRefreshButton";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { Alert } from "@material-ui/lab";
|
|
||||||
import { CircularProgress } from "@material-ui/core";
|
import { CircularProgress } from "@material-ui/core";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { Alert } from "@material-ui/lab";
|
||||||
import { RpcProcessStateType } from "models/rpcModel";
|
import { RpcProcessStateType } from "models/rpcModel";
|
||||||
|
import { useAppSelector } from "store/hooks";
|
||||||
|
|
||||||
|
// TODO: Reimplement this using Tauri
|
||||||
|
// Currently the RPC process is always available, so this component is not needed
|
||||||
|
// since the UI is only displayed when the RPC process is available
|
||||||
export default function RpcStatusAlert() {
|
export default function RpcStatusAlert() {
|
||||||
const rpcProcess = useAppSelector((s) => s.rpc.process);
|
const rpcProcess = useAppSelector((s) => s.rpc.process);
|
||||||
if (rpcProcess.type === RpcProcessStateType.STARTED) {
|
if (rpcProcess.type === RpcProcessStateType.STARTED) {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
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 {
|
import {
|
||||||
isSwapTimelockInfoCancelled,
|
isSwapTimelockInfoCancelled,
|
||||||
isSwapTimelockInfoNone,
|
isSwapTimelockInfoNone,
|
||||||
} from "models/rpcModel";
|
} from "models/rpcModel";
|
||||||
|
import { useActiveSwapInfo } from "store/hooks";
|
||||||
import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
|
import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
@ -21,6 +21,9 @@ export default function SwapMightBeCancelledAlert({
|
||||||
}: {
|
}: {
|
||||||
bobBtcLockTxConfirmations: number;
|
bobBtcLockTxConfirmations: number;
|
||||||
}) {
|
}) {
|
||||||
|
// TODO: Reimplement this using Tauri
|
||||||
|
return <></>;
|
||||||
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const swap = useActiveSwapInfo();
|
const swap = useActiveSwapInfo();
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,19 @@
|
||||||
import { Alert, AlertTitle } from "@material-ui/lab/";
|
|
||||||
import { Box, makeStyles } from "@material-ui/core";
|
import { Box, makeStyles } from "@material-ui/core";
|
||||||
|
import { Alert, AlertTitle } from "@material-ui/lab/";
|
||||||
|
import { GetSwapInfoResponse } from "models/tauriModel";
|
||||||
|
import {
|
||||||
|
BobStateName,
|
||||||
|
GetSwapInfoResponseExt,
|
||||||
|
TimelockCancel,
|
||||||
|
TimelockNone,
|
||||||
|
} from "models/tauriModelExt";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { exhaustiveGuard } from "utils/typescriptUtils";
|
import { exhaustiveGuard } from "utils/typescriptUtils";
|
||||||
|
import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
|
||||||
import {
|
import {
|
||||||
SwapCancelRefundButton,
|
SwapCancelRefundButton,
|
||||||
SwapResumeButton,
|
SwapResumeButton,
|
||||||
} from "../pages/history/table/HistoryRowActions";
|
} from "../pages/history/table/HistoryRowActions";
|
||||||
import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
|
|
||||||
import {
|
|
||||||
GetSwapInfoResponse,
|
|
||||||
GetSwapInfoResponseRunningSwap,
|
|
||||||
isGetSwapInfoResponseRunningSwap,
|
|
||||||
isSwapTimelockInfoCancelled,
|
|
||||||
isSwapTimelockInfoNone,
|
|
||||||
isSwapTimelockInfoPunished,
|
|
||||||
SwapStateName,
|
|
||||||
SwapTimelockInfoCancelled,
|
|
||||||
SwapTimelockInfoNone,
|
|
||||||
} from "../../../models/rpcModel";
|
|
||||||
import { SwapMoneroRecoveryButton } from "../pages/history/table/SwapMoneroRecoveryButton";
|
import { SwapMoneroRecoveryButton } from "../pages/history/table/SwapMoneroRecoveryButton";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
|
@ -42,7 +38,6 @@ const MessageList = ({ messages }: { messages: ReactNode[] }) => {
|
||||||
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
|
|
||||||
<li key={i}>{msg}</li>
|
<li key={i}>{msg}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -81,21 +76,21 @@ const BitcoinLockedNoTimelockExpiredStateAlert = ({
|
||||||
timelock,
|
timelock,
|
||||||
punishTimelockOffset,
|
punishTimelockOffset,
|
||||||
}: {
|
}: {
|
||||||
timelock: SwapTimelockInfoNone;
|
timelock: TimelockNone;
|
||||||
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 approximately{" "}
|
||||||
<HumanizedBitcoinBlockDuration blocks={timelock.None.blocks_left} />,
|
<HumanizedBitcoinBlockDuration blocks={timelock.content.blocks_left} />,
|
||||||
you need to refund
|
you need to refund
|
||||||
</>,
|
</>,
|
||||||
<>
|
<>
|
||||||
You will lose your funds if you do not refund or complete the swap
|
You might lose your funds if you do not refund or complete the swap
|
||||||
within{" "}
|
within{" "}
|
||||||
<HumanizedBitcoinBlockDuration
|
<HumanizedBitcoinBlockDuration
|
||||||
blocks={timelock.None.blocks_left + punishTimelockOffset}
|
blocks={timelock.content.blocks_left + punishTimelockOffset}
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
]}
|
]}
|
||||||
|
@ -113,8 +108,8 @@ const BitcoinPossiblyCancelledAlert = ({
|
||||||
swap,
|
swap,
|
||||||
timelock,
|
timelock,
|
||||||
}: {
|
}: {
|
||||||
swap: GetSwapInfoResponse;
|
swap: GetSwapInfoResponseExt;
|
||||||
timelock: SwapTimelockInfoCancelled;
|
timelock: TimelockCancel;
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
return (
|
return (
|
||||||
|
@ -124,9 +119,9 @@ const BitcoinPossiblyCancelledAlert = ({
|
||||||
"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 might lose your funds if you do not refund within{" "}
|
||||||
<HumanizedBitcoinBlockDuration
|
<HumanizedBitcoinBlockDuration
|
||||||
blocks={timelock.Cancel.blocks_left}
|
blocks={timelock.content.blocks_left}
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
]}
|
]}
|
||||||
|
@ -149,55 +144,52 @@ const ImmediateActionAlert = () => (
|
||||||
* @param swap - The swap information.
|
* @param swap - The swap information.
|
||||||
* @returns JSX.Element | null
|
* @returns JSX.Element | null
|
||||||
*/
|
*/
|
||||||
function SwapAlertStatusText({
|
function SwapAlertStatusText({ swap }: { swap: GetSwapInfoResponseExt }) {
|
||||||
swap,
|
|
||||||
}: {
|
|
||||||
swap: GetSwapInfoResponseRunningSwap;
|
|
||||||
}) {
|
|
||||||
switch (swap.state_name) {
|
switch (swap.state_name) {
|
||||||
// This is the state where the swap is safe because the other party has redeemed the Bitcoin
|
// This is the state where the swap is safe because the other party has redeemed the Bitcoin
|
||||||
// It cannot be punished anymore
|
// It cannot be punished anymore
|
||||||
case SwapStateName.BtcRedeemed:
|
case BobStateName.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 BobStateName.BtcLocked:
|
||||||
case SwapStateName.XmrLockProofReceived:
|
case BobStateName.XmrLockProofReceived:
|
||||||
case SwapStateName.XmrLocked:
|
case BobStateName.XmrLocked:
|
||||||
case SwapStateName.EncSigSent:
|
case BobStateName.EncSigSent:
|
||||||
case SwapStateName.CancelTimelockExpired:
|
case BobStateName.CancelTimelockExpired:
|
||||||
case SwapStateName.BtcCancelled:
|
case BobStateName.BtcCancelled:
|
||||||
if (swap.timelock !== null) {
|
if (swap.timelock != null) {
|
||||||
if (isSwapTimelockInfoNone(swap.timelock)) {
|
switch (swap.timelock.type) {
|
||||||
|
case "None":
|
||||||
return (
|
return (
|
||||||
<BitcoinLockedNoTimelockExpiredStateAlert
|
<BitcoinLockedNoTimelockExpiredStateAlert
|
||||||
punishTimelockOffset={swap.punish_timelock}
|
punishTimelockOffset={swap.punish_timelock}
|
||||||
timelock={swap.timelock}
|
timelock={swap.timelock}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (isSwapTimelockInfoCancelled(swap.timelock)) {
|
case "Cancel":
|
||||||
return (
|
return (
|
||||||
<BitcoinPossiblyCancelledAlert
|
<BitcoinPossiblyCancelledAlert
|
||||||
timelock={swap.timelock}
|
timelock={swap.timelock}
|
||||||
swap={swap}
|
swap={swap}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
case "Punish":
|
||||||
|
|
||||||
if (isSwapTimelockInfoPunished(swap.timelock)) {
|
|
||||||
return <ImmediateActionAlert />;
|
return <ImmediateActionAlert />;
|
||||||
}
|
|
||||||
|
|
||||||
|
default:
|
||||||
// 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);
|
exhaustiveGuard(swap.timelock);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return <ImmediateActionAlert />;
|
return <ImmediateActionAlert />;
|
||||||
default:
|
default:
|
||||||
return exhaustiveGuard(swap.state_name);
|
// TODO: fix the exhaustive guard
|
||||||
|
// return exhaustiveGuard(swap.state_name);
|
||||||
|
return <></>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,11 +201,12 @@ function SwapAlertStatusText({
|
||||||
export default function SwapStatusAlert({
|
export default function SwapStatusAlert({
|
||||||
swap,
|
swap,
|
||||||
}: {
|
}: {
|
||||||
swap: GetSwapInfoResponse;
|
swap: GetSwapInfoResponseExt;
|
||||||
}): JSX.Element | null {
|
}): JSX.Element | null {
|
||||||
// If the swap is not running, there is no need to display the alert
|
// If the swap is completed, 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)
|
// TODO: Here we should also check if the swap is in a state where any funds can be lost
|
||||||
if (!isGetSwapInfoResponseRunningSwap(swap)) {
|
// TODO: If the no Bitcoin have been locked yet, we can safely ignore the swap
|
||||||
|
if (swap.completed) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
|
||||||
|
|
||||||
export default function BitcoinIcon(props: SvgIconProps) {
|
export default function BitcoinIcon(props: SvgIconProps) {
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
||||||
<SvgIcon {...props}>
|
<SvgIcon {...props}>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
|
|
||||||
import { SvgIcon } from "@material-ui/core";
|
import { SvgIcon } from "@material-ui/core";
|
||||||
|
import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
|
||||||
|
|
||||||
export default function DiscordIcon(props: SvgIconProps) {
|
export default function DiscordIcon(props: SvgIconProps) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ReactNode } from "react";
|
|
||||||
import { IconButton } from "@material-ui/core";
|
import { IconButton } from "@material-ui/core";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
export default function LinkIconButton({
|
export default function LinkIconButton({
|
||||||
url,
|
url,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
|
||||||
|
|
||||||
export default function MoneroIcon(props: SvgIconProps) {
|
export default function MoneroIcon(props: SvgIconProps) {
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
||||||
<SvgIcon {...props}>
|
<SvgIcon {...props}>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { SvgIconProps } from "@material-ui/core/SvgIcon/SvgIcon";
|
||||||
|
|
||||||
export default function TorIcon(props: SvgIconProps) {
|
export default function TorIcon(props: SvgIconProps) {
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
||||||
<SvgIcon {...props}>
|
<SvgIcon {...props}>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { useEffect } from "react";
|
|
||||||
import { TextField } from "@material-ui/core";
|
import { TextField } from "@material-ui/core";
|
||||||
import { TextFieldProps } from "@material-ui/core/TextField/TextField";
|
import { TextFieldProps } from "@material-ui/core/TextField/TextField";
|
||||||
import { isBtcAddressValid } from "utils/conversionUtils";
|
import { useEffect } from "react";
|
||||||
import { isTestnet } from "store/config";
|
import { isTestnet } from "store/config";
|
||||||
|
import { isBtcAddressValid } from "utils/conversionUtils";
|
||||||
|
|
||||||
export default function BitcoinAddressTextField({
|
export default function BitcoinAddressTextField({
|
||||||
address,
|
address,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { useEffect } from "react";
|
|
||||||
import { TextField } from "@material-ui/core";
|
import { TextField } from "@material-ui/core";
|
||||||
import { TextFieldProps } from "@material-ui/core/TextField/TextField";
|
import { TextFieldProps } from "@material-ui/core/TextField/TextField";
|
||||||
import { isXmrAddressValid } from "utils/conversionUtils";
|
import { useEffect } from "react";
|
||||||
import { isTestnet } from "store/config";
|
import { isTestnet } from "store/config";
|
||||||
|
import { isXmrAddressValid } from "utils/conversionUtils";
|
||||||
|
|
||||||
export default function MoneroAddressTextField({
|
export default function MoneroAddressTextField({
|
||||||
address,
|
address,
|
||||||
|
|
|
@ -6,7 +6,8 @@ import {
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import IpcInvokeButton from "../IpcInvokeButton";
|
import { suspendCurrentSwap } from "renderer/rpc";
|
||||||
|
import PromiseInvokeButton from "../PromiseInvokeButton";
|
||||||
|
|
||||||
type SwapCancelAlertProps = {
|
type SwapCancelAlertProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
@ -29,15 +30,13 @@ export default function SwapSuspendAlert({
|
||||||
<Button onClick={onClose} color="primary">
|
<Button onClick={onClose} color="primary">
|
||||||
No
|
No
|
||||||
</Button>
|
</Button>
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
ipcChannel="suspend-current-swap"
|
|
||||||
ipcArgs={[]}
|
|
||||||
color="primary"
|
color="primary"
|
||||||
onSuccess={onClose}
|
onSuccess={onClose}
|
||||||
requiresRpc
|
onClick={suspendCurrentSwap}
|
||||||
>
|
>
|
||||||
Force stop
|
Force stop
|
||||||
</IpcInvokeButton>
|
</PromiseInvokeButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,15 +10,15 @@ import {
|
||||||
Select,
|
Select,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { useState } from "react";
|
import { CliLog } from "models/cliModel";
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from "notistack";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { store } from "renderer/store/storeRenderer";
|
||||||
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
||||||
import { parseDateString } from "utils/parseUtils";
|
import { parseDateString } from "utils/parseUtils";
|
||||||
import { store } from "renderer/store/storeRenderer";
|
|
||||||
import { CliLog } from "models/cliModel";
|
|
||||||
import { submitFeedbackViaHttp } from "../../../api";
|
import { submitFeedbackViaHttp } from "../../../api";
|
||||||
import { PiconeroAmount } from "../../other/Units";
|
|
||||||
import LoadingButton from "../../other/LoadingButton";
|
import LoadingButton from "../../other/LoadingButton";
|
||||||
|
import { PiconeroAmount } from "../../other/Units";
|
||||||
|
|
||||||
async function submitFeedback(body: string, swapId: string | number) {
|
async function submitFeedback(body: string, swapId: string | number) {
|
||||||
let attachedBody = "";
|
let attachedBody = "";
|
||||||
|
@ -67,7 +67,7 @@ function SwapSelectDropDown({
|
||||||
>
|
>
|
||||||
<MenuItem value={0}>Do not attach logs</MenuItem>
|
<MenuItem value={0}>Do not attach logs</MenuItem>
|
||||||
{swaps.map((swap) => (
|
{swaps.map((swap) => (
|
||||||
<MenuItem value={swap.swap_id}>
|
<MenuItem value={swap.swap_id} key={swap.swap_id}>
|
||||||
Swap {swap.swap_id.substring(0, 5)}... from{" "}
|
Swap {swap.swap_id.substring(0, 5)}... from{" "}
|
||||||
{new Date(parseDateString(swap.start_date)).toDateString()} (
|
{new Date(parseDateString(swap.start_date)).toDateString()} (
|
||||||
<PiconeroAmount amount={swap.xmr_amount} />)
|
<PiconeroAmount amount={swap.xmr_amount} />)
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { ChangeEvent, useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
DialogTitle,
|
Box,
|
||||||
|
Button,
|
||||||
|
Chip,
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
TextField,
|
DialogTitle,
|
||||||
DialogActions,
|
|
||||||
Button,
|
|
||||||
Box,
|
|
||||||
Chip,
|
|
||||||
makeStyles,
|
makeStyles,
|
||||||
|
TextField,
|
||||||
Theme,
|
Theme,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { Multiaddr } from "multiaddr";
|
import { Multiaddr } from "multiaddr";
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from "notistack";
|
||||||
import IpcInvokeButton from "../../IpcInvokeButton";
|
import { ChangeEvent, useState } from "react";
|
||||||
|
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||||
|
|
||||||
const PRESET_RENDEZVOUS_POINTS = [
|
const PRESET_RENDEZVOUS_POINTS = [
|
||||||
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
|
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
|
||||||
|
@ -53,7 +53,7 @@ export default function ListSellersDialog({
|
||||||
return "The multi address must contain the peer id (/p2p/)";
|
return "The multi address must contain the peer id (/p2p/)";
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch {
|
||||||
return "Not a valid multi address";
|
return "Not a valid multi address";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,17 +119,17 @@ export default function ListSellersDialog({
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={!(rendezvousAddress && !getMultiAddressError())}
|
disabled={!(rendezvousAddress && !getMultiAddressError())}
|
||||||
color="primary"
|
color="primary"
|
||||||
onSuccess={handleSuccess}
|
onSuccess={handleSuccess}
|
||||||
ipcChannel="spawn-list-sellers"
|
onClick={() => {
|
||||||
ipcArgs={[rendezvousAddress]}
|
throw new Error("Not implemented");
|
||||||
requiresRpc
|
}}
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
</IpcInvokeButton>
|
</PromiseInvokeButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { makeStyles, Box, Typography, Chip, Tooltip } from "@material-ui/core";
|
import { Box, Chip, makeStyles, Tooltip, Typography } from "@material-ui/core";
|
||||||
import { VerifiedUser } from "@material-ui/icons";
|
import { VerifiedUser } from "@material-ui/icons";
|
||||||
import { satsToBtc, secondsToDays } from "utils/conversionUtils";
|
|
||||||
import { ExtendedProviderStatus } from "models/apiModel";
|
import { ExtendedProviderStatus } from "models/apiModel";
|
||||||
import {
|
import {
|
||||||
MoneroBitcoinExchangeRate,
|
MoneroBitcoinExchangeRate,
|
||||||
SatsAmount,
|
SatsAmount,
|
||||||
} from "renderer/components/other/Units";
|
} from "renderer/components/other/Units";
|
||||||
|
import { satsToBtc, secondsToDays } from "utils/conversionUtils";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
content: {
|
content: {
|
||||||
|
|
|
@ -1,31 +1,31 @@
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
Button,
|
||||||
|
CircularProgress,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
DialogTitle,
|
|
||||||
Dialog,
|
|
||||||
DialogActions,
|
|
||||||
Button,
|
|
||||||
DialogContent,
|
|
||||||
makeStyles,
|
makeStyles,
|
||||||
CircularProgress,
|
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import AddIcon from "@material-ui/icons/Add";
|
import AddIcon from "@material-ui/icons/Add";
|
||||||
import { useState } from "react";
|
|
||||||
import SearchIcon from "@material-ui/icons/Search";
|
import SearchIcon from "@material-ui/icons/Search";
|
||||||
import { ExtendedProviderStatus } from "models/apiModel";
|
import { ExtendedProviderStatus } from "models/apiModel";
|
||||||
|
import { RpcMethod } from "models/rpcModel";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { setSelectedProvider } from "store/features/providersSlice";
|
||||||
import {
|
import {
|
||||||
useAllProviders,
|
useAllProviders,
|
||||||
useAppDispatch,
|
useAppDispatch,
|
||||||
useIsRpcEndpointBusy,
|
useIsRpcEndpointBusy,
|
||||||
} from "store/hooks";
|
} from "store/hooks";
|
||||||
import { setSelectedProvider } from "store/features/providersSlice";
|
|
||||||
import { RpcMethod } from "models/rpcModel";
|
|
||||||
import ProviderSubmitDialog from "./ProviderSubmitDialog";
|
|
||||||
import ListSellersDialog from "../listSellers/ListSellersDialog";
|
import ListSellersDialog from "../listSellers/ListSellersDialog";
|
||||||
import ProviderInfo from "./ProviderInfo";
|
import ProviderInfo from "./ProviderInfo";
|
||||||
|
import ProviderSubmitDialog from "./ProviderSubmitDialog";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
dialogContent: {
|
dialogContent: {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import {
|
import {
|
||||||
makeStyles,
|
Box,
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
Box,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
|
makeStyles,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import ArrowForwardIosIcon from "@material-ui/icons/ArrowForwardIos";
|
import ArrowForwardIosIcon from "@material-ui/icons/ArrowForwardIos";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { ChangeEvent, useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
DialogTitle,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
TextField,
|
TextField,
|
||||||
DialogActions,
|
|
||||||
Button,
|
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { Multiaddr } from "multiaddr";
|
import { Multiaddr } from "multiaddr";
|
||||||
|
import { ChangeEvent, useState } from "react";
|
||||||
|
|
||||||
type ProviderSubmitDialogProps = {
|
type ProviderSubmitDialogProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import QRCode from "react-qr-code";
|
|
||||||
import { Box } from "@material-ui/core";
|
import { Box } from "@material-ui/core";
|
||||||
|
import QRCode from "react-qr-code";
|
||||||
|
|
||||||
export default function BitcoinQrCode({ address }: { address: string }) {
|
export default function BitcoinQrCode({ address }: { address: string }) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import BitcoinIcon from "renderer/components/icons/BitcoinIcon";
|
||||||
import { isTestnet } from "store/config";
|
import { isTestnet } from "store/config";
|
||||||
import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
|
import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
|
||||||
import BitcoinIcon from "renderer/components/icons/BitcoinIcon";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
import TransactionInfoBox from "./TransactionInfoBox";
|
import TransactionInfoBox from "./TransactionInfoBox";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { ReactNode } from "react";
|
|
||||||
import { Box, Typography } from "@material-ui/core";
|
import { Box, Typography } from "@material-ui/core";
|
||||||
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
|
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
|
||||||
import InfoBox from "./InfoBox";
|
import { ReactNode } from "react";
|
||||||
import ClipboardIconButton from "./ClipbiardIconButton";
|
|
||||||
import BitcoinQrCode from "./BitcoinQrCode";
|
import BitcoinQrCode from "./BitcoinQrCode";
|
||||||
|
import ClipboardIconButton from "./ClipbiardIconButton";
|
||||||
|
import InfoBox from "./InfoBox";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import MoneroIcon from "renderer/components/icons/MoneroIcon";
|
||||||
import { isTestnet } from "store/config";
|
import { isTestnet } from "store/config";
|
||||||
import { getMoneroTxExplorerUrl } from "utils/conversionUtils";
|
import { getMoneroTxExplorerUrl } from "utils/conversionUtils";
|
||||||
import MoneroIcon from "renderer/components/icons/MoneroIcon";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
import TransactionInfoBox from "./TransactionInfoBox";
|
import TransactionInfoBox from "./TransactionInfoBox";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
|
@ -6,13 +5,14 @@ import {
|
||||||
DialogContent,
|
DialogContent,
|
||||||
makeStyles,
|
makeStyles,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { useAppDispatch, useAppSelector } from "store/hooks";
|
import { useState } from "react";
|
||||||
import { swapReset } from "store/features/swapSlice";
|
import { swapReset } from "store/features/swapSlice";
|
||||||
import SwapStatePage from "./pages/SwapStatePage";
|
import { useAppDispatch, useAppSelector, useIsSwapRunning } from "store/hooks";
|
||||||
import SwapStateStepper from "./SwapStateStepper";
|
|
||||||
import SwapSuspendAlert from "../SwapSuspendAlert";
|
import SwapSuspendAlert from "../SwapSuspendAlert";
|
||||||
import SwapDialogTitle from "./SwapDialogTitle";
|
|
||||||
import DebugPage from "./pages/DebugPage";
|
import DebugPage from "./pages/DebugPage";
|
||||||
|
import SwapStatePage from "./pages/SwapStatePage";
|
||||||
|
import SwapDialogTitle from "./SwapDialogTitle";
|
||||||
|
import SwapStateStepper from "./SwapStateStepper";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
content: {
|
content: {
|
||||||
|
@ -32,16 +32,17 @@ export default function SwapDialog({
|
||||||
}) {
|
}) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const swap = useAppSelector((state) => state.swap);
|
const swap = useAppSelector((state) => state.swap);
|
||||||
|
const isSwapRunning = useIsSwapRunning();
|
||||||
const [debug, setDebug] = useState(false);
|
const [debug, setDebug] = useState(false);
|
||||||
const [openSuspendAlert, setOpenSuspendAlert] = useState(false);
|
const [openSuspendAlert, setOpenSuspendAlert] = useState(false);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
function onCancel() {
|
function onCancel() {
|
||||||
if (swap.processRunning) {
|
if (isSwapRunning) {
|
||||||
setOpenSuspendAlert(true);
|
setOpenSuspendAlert(true);
|
||||||
} else {
|
} else {
|
||||||
onClose();
|
onClose();
|
||||||
setTimeout(() => dispatch(swapReset()), 0);
|
dispatch(swapReset());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ export default function SwapDialog({
|
||||||
<DebugPage />
|
<DebugPage />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<SwapStatePage swapState={swap.state} />
|
<SwapStatePage state={swap.state} />
|
||||||
<SwapStateStepper />
|
<SwapStateStepper />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -75,7 +76,7 @@ export default function SwapDialog({
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
disabled={!(swap.state !== null && !swap.processRunning)}
|
disabled={isSwapRunning}
|
||||||
>
|
>
|
||||||
Done
|
Done
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Box, DialogTitle, makeStyles, Typography } from "@material-ui/core";
|
import { Box, DialogTitle, makeStyles, Typography } from "@material-ui/core";
|
||||||
import TorStatusBadge from "./pages/TorStatusBadge";
|
|
||||||
import FeedbackSubmitBadge from "./pages/FeedbackSubmitBadge";
|
|
||||||
import DebugPageSwitchBadge from "./pages/DebugPageSwitchBadge";
|
import DebugPageSwitchBadge from "./pages/DebugPageSwitchBadge";
|
||||||
|
import FeedbackSubmitBadge from "./pages/FeedbackSubmitBadge";
|
||||||
|
import TorStatusBadge from "./pages/TorStatusBadge";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
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 { BobStateName } from "models/tauriModelExt";
|
||||||
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
import {
|
||||||
|
useActiveSwapInfo,
|
||||||
|
useAppSelector,
|
||||||
|
useIsSwapRunning,
|
||||||
|
} from "store/hooks";
|
||||||
import { exhaustiveGuard } from "utils/typescriptUtils";
|
import { exhaustiveGuard } from "utils/typescriptUtils";
|
||||||
|
|
||||||
export enum PathType {
|
export enum PathType {
|
||||||
|
@ -9,8 +13,10 @@ export enum PathType {
|
||||||
UNHAPPY_PATH = "unhappy path",
|
UNHAPPY_PATH = "unhappy path",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Consider using a TauriProgressEvent here instead of BobStateName
|
||||||
|
// TauriProgressEvent is always up to date, BobStateName is not (needs to be periodically fetched)
|
||||||
function getActiveStep(
|
function getActiveStep(
|
||||||
stateName: SwapStateName | null,
|
stateName: BobStateName | null,
|
||||||
processExited: boolean,
|
processExited: boolean,
|
||||||
): [PathType, number, boolean] {
|
): [PathType, number, boolean] {
|
||||||
switch (stateName) {
|
switch (stateName) {
|
||||||
|
@ -18,56 +24,56 @@ function getActiveStep(
|
||||||
// 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 BobStateName.Started:
|
||||||
case SwapStateName.SwapSetupCompleted:
|
case BobStateName.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 BobStateName.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 BobStateName.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 BobStateName.XmrLocked:
|
||||||
case SwapStateName.EncSigSent:
|
case BobStateName.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 BobStateName.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 BobStateName.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 BobStateName.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 BobStateName.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 BobStateName.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 BobStateName.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 BobStateName.BtcPunished:
|
||||||
return [PathType.UNHAPPY_PATH, 1, true];
|
return [PathType.UNHAPPY_PATH, 1, true];
|
||||||
default:
|
default:
|
||||||
return exhaustiveGuard(stateName);
|
return exhaustiveGuard(stateName);
|
||||||
|
@ -149,11 +155,14 @@ function UnhappyPathStepper({
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwapStateStepper() {
|
export default function SwapStateStepper() {
|
||||||
|
// TODO: There's no equivalent of this with Tauri yet.
|
||||||
const currentSwapSpawnType = useAppSelector((s) => s.swap.spawnType);
|
const currentSwapSpawnType = useAppSelector((s) => s.swap.spawnType);
|
||||||
|
|
||||||
const stateName = useActiveSwapInfo()?.state_name ?? null;
|
const stateName = useActiveSwapInfo()?.state_name ?? null;
|
||||||
const processExited = useAppSelector((s) => !s.swap.processRunning);
|
const processExited = !useIsSwapRunning();
|
||||||
const [pathType, activeStep, error] = getActiveStep(stateName, processExited);
|
const [pathType, activeStep, error] = getActiveStep(stateName, processExited);
|
||||||
|
|
||||||
|
// TODO: Fix this to work with Tauri
|
||||||
// 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} />;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
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 CliLogsBox from "../../../other/RenderedCliLog";
|
|
||||||
import JsonTreeView from "../../../other/JSONViewTree";
|
import JsonTreeView from "../../../other/JSONViewTree";
|
||||||
|
import CliLogsBox from "../../../other/RenderedCliLog";
|
||||||
|
|
||||||
export default function DebugPage() {
|
export default function DebugPage() {
|
||||||
const torStdOut = useAppSelector((s) => s.tor.stdOut);
|
const torStdOut = useAppSelector((s) => s.tor.stdOut);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { IconButton } from "@material-ui/core";
|
import { IconButton } from "@material-ui/core";
|
||||||
import FeedbackIcon from "@material-ui/icons/Feedback";
|
import FeedbackIcon from "@material-ui/icons/Feedback";
|
||||||
import FeedbackDialog from "../../feedback/FeedbackDialog";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import FeedbackDialog from "../../feedback/FeedbackDialog";
|
||||||
|
|
||||||
export default function FeedbackSubmitBadge() {
|
export default function FeedbackSubmitBadge() {
|
||||||
const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
|
const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
|
||||||
|
|
|
@ -1,43 +1,28 @@
|
||||||
import { Box } from "@material-ui/core";
|
import { Box } from "@material-ui/core";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { SwapSlice } from "models/storeModel";
|
||||||
import {
|
import CircularProgressWithSubtitle from "../CircularProgressWithSubtitle";
|
||||||
isSwapStateBtcCancelled,
|
|
||||||
isSwapStateBtcLockInMempool,
|
|
||||||
isSwapStateBtcPunished,
|
|
||||||
isSwapStateBtcRedemeed,
|
|
||||||
isSwapStateBtcRefunded,
|
|
||||||
isSwapStateInitiated,
|
|
||||||
isSwapStateProcessExited,
|
|
||||||
isSwapStateReceivedQuote,
|
|
||||||
isSwapStateStarted,
|
|
||||||
isSwapStateWaitingForBtcDeposit,
|
|
||||||
isSwapStateXmrLocked,
|
|
||||||
isSwapStateXmrLockInMempool,
|
|
||||||
isSwapStateXmrRedeemInMempool,
|
|
||||||
SwapState,
|
|
||||||
} from "../../../../../models/storeModel";
|
|
||||||
import InitiatedPage from "./init/InitiatedPage";
|
|
||||||
import WaitingForBitcoinDepositPage from "./init/WaitingForBitcoinDepositPage";
|
|
||||||
import StartedPage from "./in_progress/StartedPage";
|
|
||||||
import BitcoinLockTxInMempoolPage from "./in_progress/BitcoinLockTxInMempoolPage";
|
|
||||||
import XmrLockTxInMempoolPage from "./in_progress/XmrLockInMempoolPage";
|
|
||||||
// eslint-disable-next-line import/no-cycle
|
|
||||||
import ProcessExitedPage from "./exited/ProcessExitedPage";
|
|
||||||
import XmrRedeemInMempoolPage from "./done/XmrRedeemInMempoolPage";
|
|
||||||
import ReceivedQuotePage from "./in_progress/ReceivedQuotePage";
|
|
||||||
import BitcoinRedeemedPage from "./in_progress/BitcoinRedeemedPage";
|
|
||||||
import InitPage from "./init/InitPage";
|
|
||||||
import XmrLockedPage from "./in_progress/XmrLockedPage";
|
|
||||||
import BitcoinCancelledPage from "./in_progress/BitcoinCancelledPage";
|
|
||||||
import BitcoinRefundedPage from "./done/BitcoinRefundedPage";
|
|
||||||
import BitcoinPunishedPage from "./done/BitcoinPunishedPage";
|
import BitcoinPunishedPage from "./done/BitcoinPunishedPage";
|
||||||
import { SyncingMoneroWalletPage } from "./in_progress/SyncingMoneroWalletPage";
|
import BitcoinRefundedPage from "./done/BitcoinRefundedPage";
|
||||||
|
import XmrRedeemInMempoolPage from "./done/XmrRedeemInMempoolPage";
|
||||||
|
import ProcessExitedPage from "./exited/ProcessExitedPage";
|
||||||
|
import BitcoinCancelledPage from "./in_progress/BitcoinCancelledPage";
|
||||||
|
import BitcoinLockTxInMempoolPage from "./in_progress/BitcoinLockTxInMempoolPage";
|
||||||
|
import BitcoinRedeemedPage from "./in_progress/BitcoinRedeemedPage";
|
||||||
|
import ReceivedQuotePage from "./in_progress/ReceivedQuotePage";
|
||||||
|
import StartedPage from "./in_progress/StartedPage";
|
||||||
|
import XmrLockedPage from "./in_progress/XmrLockedPage";
|
||||||
|
import XmrLockTxInMempoolPage from "./in_progress/XmrLockInMempoolPage";
|
||||||
|
import InitiatedPage from "./init/InitiatedPage";
|
||||||
|
import InitPage from "./init/InitPage";
|
||||||
|
import WaitingForBitcoinDepositPage from "./init/WaitingForBitcoinDepositPage";
|
||||||
|
|
||||||
export default function SwapStatePage({
|
export default function SwapStatePage({
|
||||||
swapState,
|
state,
|
||||||
}: {
|
}: {
|
||||||
swapState: SwapState | null;
|
state: SwapSlice["state"];
|
||||||
}) {
|
}) {
|
||||||
|
// TODO: Reimplement this using tauri events
|
||||||
|
/*
|
||||||
const isSyncingMoneroWallet = useAppSelector(
|
const isSyncingMoneroWallet = useAppSelector(
|
||||||
(state) => state.rpc.state.moneroWallet.isSyncing,
|
(state) => state.rpc.state.moneroWallet.isSyncing,
|
||||||
);
|
);
|
||||||
|
@ -45,62 +30,57 @@ export default function SwapStatePage({
|
||||||
if (isSyncingMoneroWallet) {
|
if (isSyncingMoneroWallet) {
|
||||||
return <SyncingMoneroWalletPage />;
|
return <SyncingMoneroWalletPage />;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
if (swapState === null) {
|
if (state === null) {
|
||||||
return <InitPage />;
|
return <InitPage />;
|
||||||
}
|
}
|
||||||
if (isSwapStateInitiated(swapState)) {
|
switch (state.curr.type) {
|
||||||
|
case "Initiated":
|
||||||
return <InitiatedPage />;
|
return <InitiatedPage />;
|
||||||
}
|
case "ReceivedQuote":
|
||||||
if (isSwapStateReceivedQuote(swapState)) {
|
|
||||||
return <ReceivedQuotePage />;
|
return <ReceivedQuotePage />;
|
||||||
}
|
case "WaitingForBtcDeposit":
|
||||||
if (isSwapStateWaitingForBtcDeposit(swapState)) {
|
return <WaitingForBitcoinDepositPage {...state.curr.content} />;
|
||||||
return <WaitingForBitcoinDepositPage state={swapState} />;
|
case "Started":
|
||||||
}
|
return <StartedPage {...state.curr.content} />;
|
||||||
if (isSwapStateStarted(swapState)) {
|
case "BtcLockTxInMempool":
|
||||||
return <StartedPage state={swapState} />;
|
return <BitcoinLockTxInMempoolPage {...state.curr.content} />;
|
||||||
}
|
case "XmrLockTxInMempool":
|
||||||
if (isSwapStateBtcLockInMempool(swapState)) {
|
return <XmrLockTxInMempoolPage {...state.curr.content} />;
|
||||||
return <BitcoinLockTxInMempoolPage state={swapState} />;
|
case "XmrLocked":
|
||||||
}
|
|
||||||
if (isSwapStateXmrLockInMempool(swapState)) {
|
|
||||||
return <XmrLockTxInMempoolPage state={swapState} />;
|
|
||||||
}
|
|
||||||
if (isSwapStateXmrLocked(swapState)) {
|
|
||||||
return <XmrLockedPage />;
|
return <XmrLockedPage />;
|
||||||
}
|
case "BtcRedeemed":
|
||||||
if (isSwapStateBtcRedemeed(swapState)) {
|
|
||||||
return <BitcoinRedeemedPage />;
|
return <BitcoinRedeemedPage />;
|
||||||
}
|
case "XmrRedeemInMempool":
|
||||||
if (isSwapStateXmrRedeemInMempool(swapState)) {
|
return <XmrRedeemInMempoolPage {...state.curr.content} />;
|
||||||
return <XmrRedeemInMempoolPage state={swapState} />;
|
case "BtcCancelled":
|
||||||
}
|
|
||||||
if (isSwapStateBtcCancelled(swapState)) {
|
|
||||||
return <BitcoinCancelledPage />;
|
return <BitcoinCancelledPage />;
|
||||||
}
|
case "BtcRefunded":
|
||||||
if (isSwapStateBtcRefunded(swapState)) {
|
return <BitcoinRefundedPage {...state.curr.content} />;
|
||||||
return <BitcoinRefundedPage state={swapState} />;
|
case "BtcPunished":
|
||||||
}
|
|
||||||
if (isSwapStateBtcPunished(swapState)) {
|
|
||||||
return <BitcoinPunishedPage />;
|
return <BitcoinPunishedPage />;
|
||||||
}
|
case "AttemptingCooperativeRedeem":
|
||||||
if (isSwapStateProcessExited(swapState)) {
|
return (
|
||||||
return <ProcessExitedPage state={swapState} />;
|
<CircularProgressWithSubtitle description="Attempting to redeem the Monero with the help of the other party" />
|
||||||
}
|
|
||||||
|
|
||||||
console.error(
|
|
||||||
`No swap state page found for swap state State: ${JSON.stringify(
|
|
||||||
swapState,
|
|
||||||
null,
|
|
||||||
4,
|
|
||||||
)}`,
|
|
||||||
);
|
);
|
||||||
|
case "CooperativeRedeemAccepted":
|
||||||
|
return (
|
||||||
|
<CircularProgressWithSubtitle description="The other party is cooperating with us to redeem the Monero..." />
|
||||||
|
);
|
||||||
|
case "CooperativeRedeemRejected":
|
||||||
|
return <BitcoinPunishedPage />;
|
||||||
|
case "Released":
|
||||||
|
return <ProcessExitedPage prevState={state.prev} swapId={state.swapId} />;
|
||||||
|
default:
|
||||||
|
// TODO: Use this when we have all states implemented, ensures we don't forget to implement a state
|
||||||
|
// return exhaustiveGuard(state.curr.type);
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
No information to display
|
No information to display
|
||||||
<br />
|
<br />
|
||||||
State: ${JSON.stringify(swapState, null, 4)}
|
State: {JSON.stringify(state, null, 4)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import { Box, DialogContentText } from "@material-ui/core";
|
import { Box, DialogContentText } from "@material-ui/core";
|
||||||
import { SwapStateBtcRefunded } from "models/storeModel";
|
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||||
import { useActiveSwapInfo } from "store/hooks";
|
import { useActiveSwapInfo } from "store/hooks";
|
||||||
import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
|
|
||||||
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
|
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
|
||||||
|
import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
|
||||||
|
|
||||||
export default function BitcoinRefundedPage({
|
export default function BitcoinRefundedPage({
|
||||||
state,
|
btc_refund_txid,
|
||||||
}: {
|
}: TauriSwapProgressEventContent<"BtcRefunded">) {
|
||||||
state: SwapStateBtcRefunded | null;
|
// TODO: Reimplement this using Tauri
|
||||||
}) {
|
|
||||||
const swap = useActiveSwapInfo();
|
const swap = useActiveSwapInfo();
|
||||||
const additionalContent = swap
|
const additionalContent = swap
|
||||||
? `Refund address: ${swap.btc_refund_address}`
|
? `Refund address: ${swap.btc_refund_address}`
|
||||||
|
@ -28,14 +27,15 @@ export default function BitcoinRefundedPage({
|
||||||
gap: "0.5rem",
|
gap: "0.5rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{state && (
|
{
|
||||||
|
// TODO: We should display the confirmation count here
|
||||||
|
}
|
||||||
<BitcoinTransactionInfoBox
|
<BitcoinTransactionInfoBox
|
||||||
title="Bitcoin Refund Transaction"
|
title="Bitcoin Refund Transaction"
|
||||||
txId={state.bobBtcRefundTxId}
|
txId={btc_refund_txid}
|
||||||
loading={false}
|
loading={false}
|
||||||
additionalContent={additionalContent}
|
additionalContent={additionalContent}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
<FeedbackInfoBox />
|
<FeedbackInfoBox />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -1,23 +1,18 @@
|
||||||
import { Box, DialogContentText } from "@material-ui/core";
|
import { Box, DialogContentText } from "@material-ui/core";
|
||||||
import { SwapStateXmrRedeemInMempool } from "models/storeModel";
|
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||||
import { useActiveSwapInfo } from "store/hooks";
|
|
||||||
import { getSwapXmrAmount } from "models/rpcModel";
|
|
||||||
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
|
|
||||||
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
|
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
|
||||||
|
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
|
||||||
type XmrRedeemInMempoolPageProps = {
|
|
||||||
state: SwapStateXmrRedeemInMempool | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function XmrRedeemInMempoolPage({
|
export default function XmrRedeemInMempoolPage({
|
||||||
state,
|
xmr_redeem_address,
|
||||||
}: XmrRedeemInMempoolPageProps) {
|
xmr_redeem_txid,
|
||||||
const swap = useActiveSwapInfo();
|
}: TauriSwapProgressEventContent<"XmrRedeemInMempool">) {
|
||||||
const additionalContent = swap
|
// TODO: Reimplement this using Tauri
|
||||||
? `This transaction transfers ${getSwapXmrAmount(swap).toFixed(6)} XMR to ${
|
//const additionalContent = swap
|
||||||
state?.bobXmrRedeemAddress
|
// ? `This transaction transfers ${getSwapXmrAmount(swap).toFixed(6)} XMR to ${
|
||||||
}`
|
// state?.bobXmrRedeemAddress
|
||||||
: null;
|
// }`
|
||||||
|
// : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
@ -32,16 +27,12 @@ export default function XmrRedeemInMempoolPage({
|
||||||
gap: "0.5rem",
|
gap: "0.5rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{state && (
|
|
||||||
<>
|
|
||||||
<MoneroTransactionInfoBox
|
<MoneroTransactionInfoBox
|
||||||
title="Monero Redeem Transaction"
|
title="Monero Redeem Transaction"
|
||||||
txId={state.bobXmrRedeemTxId}
|
txId={xmr_redeem_txid}
|
||||||
additionalContent={additionalContent}
|
additionalContent={`The funds have been sent to the address ${xmr_redeem_address}`}
|
||||||
loading={false}
|
loading={false}
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<FeedbackInfoBox />
|
<FeedbackInfoBox />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Box, DialogContentText } from "@material-ui/core";
|
import { Box, DialogContentText } from "@material-ui/core";
|
||||||
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
|
||||||
import { SwapStateProcessExited } from "models/storeModel";
|
|
||||||
import CliLogsBox from "../../../../other/RenderedCliLog";
|
|
||||||
import { SwapSpawnType } from "models/cliModel";
|
import { SwapSpawnType } from "models/cliModel";
|
||||||
|
import { SwapStateProcessExited } from "models/storeModel";
|
||||||
|
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
||||||
|
import CliLogsBox from "../../../../other/RenderedCliLog";
|
||||||
|
|
||||||
export default function ProcessExitedAndNotDonePage({
|
export default function ProcessExitedAndNotDonePage({
|
||||||
state,
|
state,
|
||||||
|
@ -18,7 +18,7 @@ export default function ProcessExitedAndNotDonePage({
|
||||||
const hasRpcError = state.rpcError != null;
|
const hasRpcError = state.rpcError != null;
|
||||||
const hasSwap = swap != null;
|
const hasSwap = swap != null;
|
||||||
|
|
||||||
let messages = [];
|
const messages = [];
|
||||||
|
|
||||||
messages.push(
|
messages.push(
|
||||||
isCancelRefund
|
isCancelRefund
|
||||||
|
|
|
@ -1,47 +1,41 @@
|
||||||
import { useActiveSwapInfo } from "store/hooks";
|
import { TauriSwapProgressEvent } from "models/tauriModel";
|
||||||
import { SwapStateName } from "models/rpcModel";
|
|
||||||
import {
|
|
||||||
isSwapStateBtcPunished,
|
|
||||||
isSwapStateBtcRefunded,
|
|
||||||
isSwapStateXmrRedeemInMempool,
|
|
||||||
SwapStateProcessExited,
|
|
||||||
} from "../../../../../../models/storeModel";
|
|
||||||
import XmrRedeemInMempoolPage from "../done/XmrRedeemInMempoolPage";
|
|
||||||
import BitcoinPunishedPage from "../done/BitcoinPunishedPage";
|
|
||||||
// eslint-disable-next-line import/no-cycle
|
|
||||||
import SwapStatePage from "../SwapStatePage";
|
import SwapStatePage from "../SwapStatePage";
|
||||||
import BitcoinRefundedPage from "../done/BitcoinRefundedPage";
|
|
||||||
import ProcessExitedAndNotDonePage from "./ProcessExitedAndNotDonePage";
|
|
||||||
|
|
||||||
type ProcessExitedPageProps = {
|
export default function ProcessExitedPage({
|
||||||
state: SwapStateProcessExited;
|
prevState,
|
||||||
};
|
swapId,
|
||||||
|
}: {
|
||||||
export default function ProcessExitedPage({ state }: ProcessExitedPageProps) {
|
prevState: TauriSwapProgressEvent | null;
|
||||||
const swap = useActiveSwapInfo();
|
swapId: string;
|
||||||
|
}) {
|
||||||
// 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 previous state, we can show the user the last state of the swap
|
||||||
|
// We only show the last state if its a final state (XmrRedeemInMempool, BtcRefunded, BtcPunished)
|
||||||
if (
|
if (
|
||||||
isSwapStateXmrRedeemInMempool(state.prevState) ||
|
prevState != null &&
|
||||||
isSwapStateBtcRefunded(state.prevState) ||
|
(prevState.type === "XmrRedeemInMempool" ||
|
||||||
isSwapStateBtcPunished(state.prevState)
|
prevState.type === "BtcRefunded" ||
|
||||||
|
prevState.type === "BtcPunished")
|
||||||
) {
|
) {
|
||||||
return <SwapStatePage swapState={state.prevState} />;
|
return (
|
||||||
|
<SwapStatePage
|
||||||
|
state={{
|
||||||
|
curr: prevState,
|
||||||
|
prev: null,
|
||||||
|
swapId,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// TODO: Display something useful here
|
||||||
if (swap) {
|
return (
|
||||||
if (swap.state_name === SwapStateName.XmrRedeemed) {
|
<>
|
||||||
return <XmrRedeemInMempoolPage state={null} />;
|
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 (swap.state_name === SwapStateName.BtcRefunded) {
|
Not implemented yet
|
||||||
return <BitcoinRefundedPage state={null} />;
|
</>
|
||||||
}
|
);
|
||||||
if (swap.state_name === SwapStateName.BtcPunished) {
|
|
||||||
return <BitcoinPunishedPage />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the swap is not a "done" state (or we don't have a db state because the swap did complete the SwapSetup yet) we should tell the user and show logs
|
// 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,19 +1,16 @@
|
||||||
import { Box, DialogContentText } from "@material-ui/core";
|
import { Box, DialogContentText } from "@material-ui/core";
|
||||||
import { SwapStateBtcLockInMempool } from "models/storeModel";
|
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||||
import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
|
|
||||||
import SwapMightBeCancelledAlert from "../../../../alert/SwapMightBeCancelledAlert";
|
import SwapMightBeCancelledAlert from "../../../../alert/SwapMightBeCancelledAlert";
|
||||||
|
import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
|
||||||
type BitcoinLockTxInMempoolPageProps = {
|
|
||||||
state: SwapStateBtcLockInMempool;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function BitcoinLockTxInMempoolPage({
|
export default function BitcoinLockTxInMempoolPage({
|
||||||
state,
|
btc_lock_confirmations,
|
||||||
}: BitcoinLockTxInMempoolPageProps) {
|
btc_lock_txid,
|
||||||
|
}: TauriSwapProgressEventContent<"BtcLockTxInMempool">) {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<SwapMightBeCancelledAlert
|
<SwapMightBeCancelledAlert
|
||||||
bobBtcLockTxConfirmations={state.bobBtcLockTxConfirmations}
|
bobBtcLockTxConfirmations={btc_lock_confirmations}
|
||||||
/>
|
/>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
The Bitcoin lock transaction has been published. The swap will proceed
|
The Bitcoin lock transaction has been published. The swap will proceed
|
||||||
|
@ -22,14 +19,14 @@ export default function BitcoinLockTxInMempoolPage({
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<BitcoinTransactionInfoBox
|
<BitcoinTransactionInfoBox
|
||||||
title="Bitcoin Lock Transaction"
|
title="Bitcoin Lock Transaction"
|
||||||
txId={state.bobBtcLockTxId}
|
txId={btc_lock_txid}
|
||||||
loading
|
loading
|
||||||
additionalContent={
|
additionalContent={
|
||||||
<>
|
<>
|
||||||
Most swap providers require one confirmation before locking their
|
Most swap providers require one confirmation before locking their
|
||||||
Monero
|
Monero
|
||||||
<br />
|
<br />
|
||||||
Confirmations: {state.bobBtcLockTxConfirmations}
|
Confirmations: {btc_lock_confirmations}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import { SwapStateStarted } from "models/storeModel";
|
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||||
import { BitcoinAmount } from "renderer/components/other/Units";
|
import { SatsAmount } from "renderer/components/other/Units";
|
||||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||||
|
|
||||||
export default function StartedPage({ state }: { state: SwapStateStarted }) {
|
export default function StartedPage({
|
||||||
const description = state.txLockDetails ? (
|
btc_lock_amount,
|
||||||
|
btc_tx_lock_fee,
|
||||||
|
}: TauriSwapProgressEventContent<"Started">) {
|
||||||
|
return (
|
||||||
|
<CircularProgressWithSubtitle
|
||||||
|
description={
|
||||||
<>
|
<>
|
||||||
Locking <BitcoinAmount amount={state.txLockDetails.amount} /> with a
|
Locking <SatsAmount amount={btc_lock_amount} /> with a network fee of{" "}
|
||||||
network fee of <BitcoinAmount amount={state.txLockDetails.fees} />
|
<SatsAmount amount={btc_tx_lock_fee} />
|
||||||
</>
|
</>
|
||||||
) : (
|
}
|
||||||
"Locking Bitcoin"
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return <CircularProgressWithSubtitle description={description} />;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
import { Box, DialogContentText } from "@material-ui/core";
|
import { Box, DialogContentText } from "@material-ui/core";
|
||||||
import { SwapStateXmrLockInMempool } from "models/storeModel";
|
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||||
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
|
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
|
||||||
|
|
||||||
type XmrLockTxInMempoolPageProps = {
|
|
||||||
state: SwapStateXmrLockInMempool;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function XmrLockTxInMempoolPage({
|
export default function XmrLockTxInMempoolPage({
|
||||||
state,
|
xmr_lock_tx_confirmations,
|
||||||
}: XmrLockTxInMempoolPageProps) {
|
xmr_lock_txid,
|
||||||
const additionalContent = `Confirmations: ${state.aliceXmrLockTxConfirmations}/10`;
|
}: TauriSwapProgressEventContent<"XmrLockTxInMempool">) {
|
||||||
|
const additionalContent = `Confirmations: ${xmr_lock_tx_confirmations}/10`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
@ -20,7 +17,7 @@ export default function XmrLockTxInMempoolPage({
|
||||||
|
|
||||||
<MoneroTransactionInfoBox
|
<MoneroTransactionInfoBox
|
||||||
title="Monero Lock Transaction"
|
title="Monero Lock Transaction"
|
||||||
txId={state.aliceXmrLockTxId}
|
txId={xmr_lock_txid}
|
||||||
additionalContent={additionalContent}
|
additionalContent={additionalContent}
|
||||||
loading
|
loading
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { useState } from "react";
|
|
||||||
import { Box, makeStyles, TextField, Typography } from "@material-ui/core";
|
import { Box, makeStyles, TextField, Typography } from "@material-ui/core";
|
||||||
import { SwapStateWaitingForBtcDeposit } from "models/storeModel";
|
import { BidQuote } from "models/tauriModel";
|
||||||
|
import { useState } from "react";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
import { satsToBtc } from "utils/conversionUtils";
|
import { btcToSats, satsToBtc } from "utils/conversionUtils";
|
||||||
import { MoneroAmount } from "../../../../other/Units";
|
import { MoneroAmount } from "../../../../other/Units";
|
||||||
|
|
||||||
const MONERO_FEE = 0.000016;
|
const MONERO_FEE = 0.000016;
|
||||||
|
@ -29,42 +29,42 @@ function calcBtcAmountWithoutFees(amount: number, fees: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DepositAmountHelper({
|
export default function DepositAmountHelper({
|
||||||
state,
|
min_deposit_until_swap_will_start,
|
||||||
|
max_deposit_until_maximum_amount_is_reached,
|
||||||
|
min_bitcoin_lock_tx_fee,
|
||||||
|
quote,
|
||||||
}: {
|
}: {
|
||||||
state: SwapStateWaitingForBtcDeposit;
|
min_deposit_until_swap_will_start: number;
|
||||||
|
max_deposit_until_maximum_amount_is_reached: number;
|
||||||
|
min_bitcoin_lock_tx_fee: number;
|
||||||
|
quote: BidQuote;
|
||||||
}) {
|
}) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [amount, setAmount] = useState(state.minDeposit);
|
const [amount, setAmount] = useState(min_deposit_until_swap_will_start);
|
||||||
const bitcoinBalance = useAppSelector((s) => s.rpc.state.balance) || 0;
|
const bitcoinBalance = useAppSelector((s) => s.rpc.state.balance) || 0;
|
||||||
|
|
||||||
function getTotalAmountAfterDeposit() {
|
function getTotalAmountAfterDeposit() {
|
||||||
return amount + satsToBtc(bitcoinBalance);
|
return amount + bitcoinBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasError() {
|
function hasError() {
|
||||||
return (
|
return (
|
||||||
amount < state.minDeposit ||
|
amount < min_deposit_until_swap_will_start ||
|
||||||
getTotalAmountAfterDeposit() > state.maximumAmount
|
getTotalAmountAfterDeposit() > max_deposit_until_maximum_amount_is_reached
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcXMRAmount(): number | null {
|
function calcXMRAmount(): number | null {
|
||||||
if (Number.isNaN(amount)) return null;
|
if (Number.isNaN(amount)) return null;
|
||||||
if (hasError()) return null;
|
if (hasError()) return null;
|
||||||
if (state.price == null) return null;
|
if (quote.price == null) return null;
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Calculating calcBtcAmountWithoutFees(${getTotalAmountAfterDeposit()}, ${
|
|
||||||
state.minBitcoinLockTxFee
|
|
||||||
}) / ${state.price} - ${MONERO_FEE}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
calcBtcAmountWithoutFees(
|
calcBtcAmountWithoutFees(
|
||||||
getTotalAmountAfterDeposit(),
|
getTotalAmountAfterDeposit(),
|
||||||
state.minBitcoinLockTxFee,
|
min_bitcoin_lock_tx_fee,
|
||||||
) /
|
) /
|
||||||
state.price -
|
quote.price -
|
||||||
MONERO_FEE
|
MONERO_FEE
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -75,9 +75,9 @@ export default function DepositAmountHelper({
|
||||||
Depositing {bitcoinBalance > 0 && <>another</>}
|
Depositing {bitcoinBalance > 0 && <>another</>}
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
error={hasError()}
|
error={!!hasError()}
|
||||||
value={amount}
|
value={satsToBtc(amount)}
|
||||||
onChange={(e) => setAmount(parseFloat(e.target.value))}
|
onChange={(e) => setAmount(btcToSats(parseFloat(e.target.value)))}
|
||||||
size="small"
|
size="small"
|
||||||
type="number"
|
type="number"
|
||||||
className={classes.textField}
|
className={classes.textField}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
|
||||||
import { MoneroWalletRpcUpdateState } from "../../../../../../models/storeModel";
|
import { MoneroWalletRpcUpdateState } from "../../../../../../models/storeModel";
|
||||||
|
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||||
|
|
||||||
export default function DownloadingMoneroWalletRpcPage({
|
export default function DownloadingMoneroWalletRpcPage({
|
||||||
updateState,
|
updateState,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { Box, DialogContentText, makeStyles } from "@material-ui/core";
|
import { Box, DialogContentText, makeStyles } from "@material-ui/core";
|
||||||
|
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import BitcoinAddressTextField from "renderer/components/inputs/BitcoinAddressTextField";
|
import BitcoinAddressTextField from "renderer/components/inputs/BitcoinAddressTextField";
|
||||||
import MoneroAddressTextField from "renderer/components/inputs/MoneroAddressTextField";
|
import MoneroAddressTextField from "renderer/components/inputs/MoneroAddressTextField";
|
||||||
|
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||||
|
import { buyXmr } from "renderer/rpc";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
|
||||||
import { isTestnet } from "store/config";
|
|
||||||
import RemainingFundsWillBeUsedAlert from "../../../../alert/RemainingFundsWillBeUsedAlert";
|
import RemainingFundsWillBeUsedAlert from "../../../../alert/RemainingFundsWillBeUsedAlert";
|
||||||
import IpcInvokeButton from "../../../../IpcInvokeButton";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
initButton: {
|
initButton: {
|
||||||
|
@ -29,6 +29,10 @@ export default function InitPage() {
|
||||||
(state) => state.providers.selectedProvider,
|
(state) => state.providers.selectedProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
await buyXmr(selectedProvider, refundAddress, redeemAddress);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<RemainingFundsWillBeUsedAlert />
|
<RemainingFundsWillBeUsedAlert />
|
||||||
|
@ -58,7 +62,7 @@ export default function InitPage() {
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
disabled={
|
disabled={
|
||||||
!refundAddressValid || !redeemAddressValid || !selectedProvider
|
!refundAddressValid || !redeemAddressValid || !selectedProvider
|
||||||
}
|
}
|
||||||
|
@ -67,12 +71,10 @@ export default function InitPage() {
|
||||||
size="large"
|
size="large"
|
||||||
className={classes.initButton}
|
className={classes.initButton}
|
||||||
endIcon={<PlayArrowIcon />}
|
endIcon={<PlayArrowIcon />}
|
||||||
ipcChannel="spawn-buy-xmr"
|
onClick={init}
|
||||||
ipcArgs={[selectedProvider, redeemAddress, refundAddress]}
|
|
||||||
displayErrorSnackbar={false}
|
|
||||||
>
|
>
|
||||||
Start swap
|
Start swap
|
||||||
</IpcInvokeButton>
|
</PromiseInvokeButton>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useAppSelector } from "store/hooks";
|
|
||||||
import { SwapSpawnType } from "models/cliModel";
|
import { SwapSpawnType } from "models/cliModel";
|
||||||
|
import { useAppSelector } from "store/hooks";
|
||||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||||
|
|
||||||
export default function InitiatedPage() {
|
export default function InitiatedPage() {
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import { Box, makeStyles, Typography } from "@material-ui/core";
|
import { Box, makeStyles, Typography } from "@material-ui/core";
|
||||||
import { SwapStateWaitingForBtcDeposit } from "models/storeModel";
|
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
import DepositAddressInfoBox from "../../DepositAddressInfoBox";
|
|
||||||
import BitcoinIcon from "../../../../icons/BitcoinIcon";
|
import BitcoinIcon from "../../../../icons/BitcoinIcon";
|
||||||
|
import { MoneroSatsExchangeRate, SatsAmount } from "../../../../other/Units";
|
||||||
|
import DepositAddressInfoBox from "../../DepositAddressInfoBox";
|
||||||
import DepositAmountHelper from "./DepositAmountHelper";
|
import DepositAmountHelper from "./DepositAmountHelper";
|
||||||
import {
|
|
||||||
BitcoinAmount,
|
|
||||||
MoneroBitcoinExchangeRate,
|
|
||||||
SatsAmount,
|
|
||||||
} from "../../../../other/Units";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
amountHelper: {
|
amountHelper: {
|
||||||
|
@ -23,13 +19,13 @@ const useStyles = makeStyles((theme) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
type WaitingForBtcDepositPageProps = {
|
|
||||||
state: SwapStateWaitingForBtcDeposit;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function WaitingForBtcDepositPage({
|
export default function WaitingForBtcDepositPage({
|
||||||
state,
|
deposit_address,
|
||||||
}: WaitingForBtcDepositPageProps) {
|
min_deposit_until_swap_will_start,
|
||||||
|
max_deposit_until_maximum_amount_is_reached,
|
||||||
|
min_bitcoin_lock_tx_fee,
|
||||||
|
quote,
|
||||||
|
}: TauriSwapProgressEventContent<"WaitingForBtcDeposit">) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const bitcoinBalance = useAppSelector((s) => s.rpc.state.balance) || 0;
|
const bitcoinBalance = useAppSelector((s) => s.rpc.state.balance) || 0;
|
||||||
|
|
||||||
|
@ -38,7 +34,7 @@ export default function WaitingForBtcDepositPage({
|
||||||
<Box>
|
<Box>
|
||||||
<DepositAddressInfoBox
|
<DepositAddressInfoBox
|
||||||
title="Bitcoin Deposit Address"
|
title="Bitcoin Deposit Address"
|
||||||
address={state.depositAddress}
|
address={deposit_address}
|
||||||
additionalContent={
|
additionalContent={
|
||||||
<Box className={classes.additionalContent}>
|
<Box className={classes.additionalContent}>
|
||||||
<Typography variant="subtitle2">
|
<Typography variant="subtitle2">
|
||||||
|
@ -51,9 +47,11 @@ export default function WaitingForBtcDepositPage({
|
||||||
) : null}
|
) : null}
|
||||||
<li>
|
<li>
|
||||||
Send any amount between{" "}
|
Send any amount between{" "}
|
||||||
<BitcoinAmount amount={state.minDeposit} /> and{" "}
|
<SatsAmount amount={min_deposit_until_swap_will_start} /> and{" "}
|
||||||
<BitcoinAmount amount={state.maxDeposit} /> to the address
|
<SatsAmount
|
||||||
above
|
amount={max_deposit_until_maximum_amount_is_reached}
|
||||||
|
/>{" "}
|
||||||
|
to the address above
|
||||||
{bitcoinBalance > 0 && (
|
{bitcoinBalance > 0 && (
|
||||||
<> (on top of the already deposited funds)</>
|
<> (on top of the already deposited funds)</>
|
||||||
)}
|
)}
|
||||||
|
@ -61,11 +59,11 @@ export default function WaitingForBtcDepositPage({
|
||||||
<li>
|
<li>
|
||||||
All Bitcoin sent to this this address will converted into
|
All Bitcoin sent to this this address will converted into
|
||||||
Monero at an exchance rate of{" "}
|
Monero at an exchance rate of{" "}
|
||||||
<MoneroBitcoinExchangeRate rate={state.price} />
|
<MoneroSatsExchangeRate rate={quote.price} />
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
The network fee of{" "}
|
The network fee of{" "}
|
||||||
<BitcoinAmount amount={state.minBitcoinLockTxFee} /> will
|
<SatsAmount amount={min_bitcoin_lock_tx_fee} /> will
|
||||||
automatically be deducted from the deposited coins
|
automatically be deducted from the deposited coins
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
@ -74,7 +72,16 @@ export default function WaitingForBtcDepositPage({
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Typography>
|
</Typography>
|
||||||
<DepositAmountHelper state={state} />
|
<DepositAmountHelper
|
||||||
|
min_deposit_until_swap_will_start={
|
||||||
|
min_deposit_until_swap_will_start
|
||||||
|
}
|
||||||
|
max_deposit_until_maximum_amount_is_reached={
|
||||||
|
max_deposit_until_maximum_amount_is_reached
|
||||||
|
}
|
||||||
|
min_bitcoin_lock_tx_fee={min_bitcoin_lock_tx_fee}
|
||||||
|
quote={quote}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
icon={<BitcoinIcon />}
|
icon={<BitcoinIcon />}
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import { Button, Dialog, DialogActions } from "@material-ui/core";
|
import { Button, Dialog, DialogActions } from "@material-ui/core";
|
||||||
import { useAppDispatch, useIsRpcEndpointBusy } from "store/hooks";
|
|
||||||
import { RpcMethod } from "models/rpcModel";
|
|
||||||
import { rpcResetWithdrawTxId } from "store/features/rpcSlice";
|
|
||||||
import WithdrawStatePage from "./WithdrawStatePage";
|
|
||||||
import DialogHeader from "../DialogHeader";
|
|
||||||
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||||
import { withdrawBtc } from "renderer/rpc";
|
import { withdrawBtc } from "renderer/rpc";
|
||||||
import BtcTxInMempoolPageContent from "./pages/BitcoinWithdrawTxInMempoolPage";
|
import DialogHeader from "../DialogHeader";
|
||||||
import AddressInputPage from "./pages/AddressInputPage";
|
import AddressInputPage from "./pages/AddressInputPage";
|
||||||
|
import BtcTxInMempoolPageContent from "./pages/BitcoinWithdrawTxInMempoolPage";
|
||||||
import WithdrawDialogContent from "./WithdrawDialogContent";
|
import WithdrawDialogContent from "./WithdrawDialogContent";
|
||||||
|
|
||||||
export default function WithdrawDialog({
|
export default function WithdrawDialog({
|
||||||
|
@ -42,10 +38,7 @@ export default function WithdrawDialog({
|
||||||
setWithdrawAddressValid={setWithdrawAddressValid}
|
setWithdrawAddressValid={setWithdrawAddressValid}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<BtcTxInMempoolPageContent
|
<BtcTxInMempoolPageContent withdrawTxId={withdrawTxId} />
|
||||||
withdrawTxId={withdrawTxId}
|
|
||||||
onCancel={onCancel}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</WithdrawDialogContent>
|
</WithdrawDialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ReactNode } from "react";
|
|
||||||
import { Box, DialogContent, makeStyles } from "@material-ui/core";
|
import { Box, DialogContent, makeStyles } from "@material-ui/core";
|
||||||
|
import { ReactNode } from "react";
|
||||||
import WithdrawStepper from "./WithdrawStepper";
|
import WithdrawStepper from "./WithdrawStepper";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Step, StepLabel, Stepper } from "@material-ui/core";
|
import { Step, StepLabel, Stepper } from "@material-ui/core";
|
||||||
import { useAppSelector, useIsRpcEndpointBusy } from "store/hooks";
|
|
||||||
|
|
||||||
function getActiveStep(isPending: boolean, withdrawTxId: string | null) {
|
function getActiveStep(isPending: boolean, withdrawTxId: string | null) {
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import { useState } from "react";
|
import { DialogContentText } from "@material-ui/core";
|
||||||
import { Button, DialogActions, DialogContentText } from "@material-ui/core";
|
|
||||||
import BitcoinAddressTextField from "../../../inputs/BitcoinAddressTextField";
|
import BitcoinAddressTextField from "../../../inputs/BitcoinAddressTextField";
|
||||||
import WithdrawDialogContent from "../WithdrawDialogContent";
|
|
||||||
import IpcInvokeButton from "../../../IpcInvokeButton";
|
|
||||||
|
|
||||||
export default function AddressInputPage({
|
export default function AddressInputPage({
|
||||||
withdrawAddress,
|
withdrawAddress,
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import { Button, DialogActions, DialogContentText } from "@material-ui/core";
|
import { DialogContentText } from "@material-ui/core";
|
||||||
import BitcoinTransactionInfoBox from "../../swap/BitcoinTransactionInfoBox";
|
import BitcoinTransactionInfoBox from "../../swap/BitcoinTransactionInfoBox";
|
||||||
import WithdrawDialogContent from "../WithdrawDialogContent";
|
|
||||||
|
|
||||||
export default function BtcTxInMempoolPageContent({
|
export default function BtcTxInMempoolPageContent({
|
||||||
withdrawTxId,
|
withdrawTxId,
|
||||||
onCancel,
|
|
||||||
}: {
|
}: {
|
||||||
withdrawTxId: string;
|
withdrawTxId: string;
|
||||||
onCancel: () => void;
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -3,7 +3,6 @@ import GitHubIcon from "@material-ui/icons/GitHub";
|
||||||
import RedditIcon from "@material-ui/icons/Reddit";
|
import RedditIcon from "@material-ui/icons/Reddit";
|
||||||
import FundsLeftInWalletAlert from "../alert/FundsLeftInWalletAlert";
|
import FundsLeftInWalletAlert from "../alert/FundsLeftInWalletAlert";
|
||||||
import MoneroWalletRpcUpdatingAlert from "../alert/MoneroWalletRpcUpdatingAlert";
|
import MoneroWalletRpcUpdatingAlert from "../alert/MoneroWalletRpcUpdatingAlert";
|
||||||
import RpcStatusAlert from "../alert/RpcStatusAlert";
|
|
||||||
import UnfinishedSwapsAlert from "../alert/UnfinishedSwapsAlert";
|
import UnfinishedSwapsAlert from "../alert/UnfinishedSwapsAlert";
|
||||||
import DiscordIcon from "../icons/DiscordIcon";
|
import DiscordIcon from "../icons/DiscordIcon";
|
||||||
import LinkIconButton from "../icons/LinkIconButton";
|
import LinkIconButton from "../icons/LinkIconButton";
|
||||||
|
@ -29,7 +28,11 @@ export default function NavigationFooter() {
|
||||||
<Box className={classes.outer}>
|
<Box className={classes.outer}>
|
||||||
<FundsLeftInWalletAlert />
|
<FundsLeftInWalletAlert />
|
||||||
<UnfinishedSwapsAlert />
|
<UnfinishedSwapsAlert />
|
||||||
<RpcStatusAlert />
|
|
||||||
|
{
|
||||||
|
// TODO: Uncomment when we have implemented a way for the UI to be displayed before the context has been initialized
|
||||||
|
// <RpcStatusAlert />
|
||||||
|
}
|
||||||
<MoneroWalletRpcUpdatingAlert />
|
<MoneroWalletRpcUpdatingAlert />
|
||||||
<Box className={classes.linksOuter}>
|
<Box className={classes.linksOuter}>
|
||||||
<LinkIconButton url="https://reddit.com/r/unstoppableswap">
|
<LinkIconButton url="https://reddit.com/r/unstoppableswap">
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Box, Divider, IconButton, Paper, Typography } from "@material-ui/core";
|
import { Box, Divider, IconButton, Paper, Typography } from "@material-ui/core";
|
||||||
import { ReactNode, useRef } from "react";
|
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
|
||||||
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 { ReactNode, useRef } from "react";
|
||||||
import { VList, VListHandle } from "virtua";
|
import { VList, VListHandle } from "virtua";
|
||||||
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
|
|
||||||
import { ExpandableSearchBox } from "./ExpandableSearchBox";
|
import { ExpandableSearchBox } from "./ExpandableSearchBox";
|
||||||
|
|
||||||
const MIN_HEIGHT = "10rem";
|
const MIN_HEIGHT = "10rem";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { piconerosToXmr, satsToBtc } from "utils/conversionUtils";
|
|
||||||
import { Tooltip } from "@material-ui/core";
|
import { Tooltip } from "@material-ui/core";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
|
import { piconerosToXmr, satsToBtc } from "utils/conversionUtils";
|
||||||
|
|
||||||
type Amount = number | null | undefined;
|
type Amount = number | null | undefined;
|
||||||
|
|
||||||
|
@ -64,10 +64,27 @@ export function MoneroAmount({ amount }: { amount: Amount }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MoneroBitcoinExchangeRate({ rate }: { rate: Amount }) {
|
export function MoneroBitcoinExchangeRate(
|
||||||
|
state: { rate: Amount } | { satsAmount: number; piconeroAmount: number },
|
||||||
|
) {
|
||||||
|
if ("rate" in state) {
|
||||||
|
return (
|
||||||
|
<AmountWithUnit amount={state.rate} unit="BTC/XMR" fixedPrecision={8} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rate =
|
||||||
|
satsToBtc(state.satsAmount) / piconerosToXmr(state.piconeroAmount);
|
||||||
|
|
||||||
return <AmountWithUnit amount={rate} unit="BTC/XMR" fixedPrecision={8} />;
|
return <AmountWithUnit amount={rate} unit="BTC/XMR" fixedPrecision={8} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function MoneroSatsExchangeRate({ rate }: { rate: Amount }) {
|
||||||
|
const btc = satsToBtc(rate);
|
||||||
|
|
||||||
|
return <AmountWithUnit amount={btc} unit="BTC/XMR" fixedPrecision={6} />;
|
||||||
|
}
|
||||||
|
|
||||||
export function SatsAmount({ amount }: { amount: Amount }) {
|
export function SatsAmount({ amount }: { amount: Amount }) {
|
||||||
const btcAmount = amount == null ? null : satsToBtc(amount);
|
const btcAmount = amount == null ? null : satsToBtc(amount);
|
||||||
return <BitcoinAmount amount={btcAmount} />;
|
return <BitcoinAmount amount={btcAmount} />;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import FolderOpenIcon from "@material-ui/icons/FolderOpen";
|
||||||
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
||||||
import StopIcon from "@material-ui/icons/Stop";
|
import StopIcon from "@material-ui/icons/Stop";
|
||||||
import { RpcProcessStateType } from "models/rpcModel";
|
import { RpcProcessStateType } from "models/rpcModel";
|
||||||
import IpcInvokeButton from "renderer/components/IpcInvokeButton";
|
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
import InfoBox from "../../modal/swap/InfoBox";
|
import InfoBox from "../../modal/swap/InfoBox";
|
||||||
import CliLogsBox from "../../other/RenderedCliLog";
|
import CliLogsBox from "../../other/RenderedCliLog";
|
||||||
|
@ -36,34 +36,34 @@ export default function RpcControlBox() {
|
||||||
}
|
}
|
||||||
additionalContent={
|
additionalContent={
|
||||||
<Box className={classes.actionsOuter}>
|
<Box className={classes.actionsOuter}>
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
ipcChannel="spawn-start-rpc"
|
|
||||||
ipcArgs={[]}
|
|
||||||
endIcon={<PlayArrowIcon />}
|
endIcon={<PlayArrowIcon />}
|
||||||
disabled={isRunning}
|
disabled={isRunning}
|
||||||
requiresRpc={false}
|
onClick={() => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Start Daemon
|
Start Daemon
|
||||||
</IpcInvokeButton>
|
</PromiseInvokeButton>
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
ipcChannel="stop-cli"
|
|
||||||
ipcArgs={[]}
|
|
||||||
endIcon={<StopIcon />}
|
endIcon={<StopIcon />}
|
||||||
disabled={!isRunning}
|
disabled={!isRunning}
|
||||||
requiresRpc={false}
|
onClick={() => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Stop Daemon
|
Stop Daemon
|
||||||
</IpcInvokeButton>
|
</PromiseInvokeButton>
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
ipcChannel="open-data-dir-in-file-explorer"
|
|
||||||
ipcArgs={[]}
|
|
||||||
endIcon={<FolderOpenIcon />}
|
endIcon={<FolderOpenIcon />}
|
||||||
requiresRpc={false}
|
|
||||||
isIconButton
|
isIconButton
|
||||||
size="small"
|
size="small"
|
||||||
tooltipTitle="Open the data directory of the Swap Daemon in your file explorer"
|
tooltipTitle="Open the data directory of the Swap Daemon in your file explorer"
|
||||||
|
onClick={() => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Box, makeStyles, Typography } from "@material-ui/core";
|
import { Box, makeStyles, Typography } from "@material-ui/core";
|
||||||
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
||||||
import StopIcon from "@material-ui/icons/Stop";
|
import StopIcon from "@material-ui/icons/Stop";
|
||||||
import IpcInvokeButton from "renderer/components/IpcInvokeButton";
|
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
import InfoBox from "../../modal/swap/InfoBox";
|
import InfoBox from "../../modal/swap/InfoBox";
|
||||||
import CliLogsBox from "../../other/RenderedCliLog";
|
import CliLogsBox from "../../other/RenderedCliLog";
|
||||||
|
@ -42,26 +42,26 @@ export default function TorInfoBox() {
|
||||||
}
|
}
|
||||||
additionalContent={
|
additionalContent={
|
||||||
<Box className={classes.actionsOuter}>
|
<Box className={classes.actionsOuter}>
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={isTorRunning}
|
disabled={isTorRunning}
|
||||||
ipcChannel="spawn-tor"
|
|
||||||
ipcArgs={[]}
|
|
||||||
endIcon={<PlayArrowIcon />}
|
endIcon={<PlayArrowIcon />}
|
||||||
requiresRpc={false}
|
onClick={() => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Start Tor
|
Start Tor
|
||||||
</IpcInvokeButton>
|
</PromiseInvokeButton>
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={!isTorRunning}
|
disabled={!isTorRunning}
|
||||||
ipcChannel="stop-tor"
|
|
||||||
ipcArgs={[]}
|
|
||||||
endIcon={<StopIcon />}
|
endIcon={<StopIcon />}
|
||||||
requiresRpc={false}
|
onClick={() => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Stop Tor
|
Stop Tor
|
||||||
</IpcInvokeButton>
|
</PromiseInvokeButton>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
icon={null}
|
icon={null}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Typography } from "@material-ui/core";
|
import { Typography } from "@material-ui/core";
|
||||||
import { useIsSwapRunning } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
import SwapTxLockAlertsBox from "../../alert/SwapTxLockAlertsBox";
|
import SwapTxLockAlertsBox from "../../alert/SwapTxLockAlertsBox";
|
||||||
import SwapDialog from "../../modal/swap/SwapDialog";
|
import SwapDialog from "../../modal/swap/SwapDialog";
|
||||||
import HistoryTable from "./table/HistoryTable";
|
import HistoryTable from "./table/HistoryTable";
|
||||||
|
|
||||||
export default function HistoryPage() {
|
export default function HistoryPage() {
|
||||||
const showDialog = useIsSwapRunning();
|
const showDialog = useAppSelector((state) => state.swap.state !== null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -6,23 +6,14 @@ import {
|
||||||
TableCell,
|
TableCell,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
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 { GetSwapInfoResponse } from "models/tauriModel";
|
||||||
getHumanReadableDbStateType,
|
import { useState } from "react";
|
||||||
getSwapBtcAmount,
|
import { PiconeroAmount, SatsAmount } from "../../../other/Units";
|
||||||
getSwapXmrAmount,
|
|
||||||
GetSwapInfoResponse,
|
|
||||||
} 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";
|
|
||||||
|
|
||||||
type HistoryRowProps = {
|
|
||||||
swap: GetSwapInfoResponse;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
amountTransferContainer: {
|
amountTransferContainer: {
|
||||||
|
@ -43,17 +34,14 @@ function AmountTransfer({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={classes.amountTransferContainer}>
|
<Box className={classes.amountTransferContainer}>
|
||||||
<BitcoinAmount amount={btcAmount} />
|
<SatsAmount amount={btcAmount} />
|
||||||
<ArrowForwardIcon />
|
<ArrowForwardIcon />
|
||||||
<MoneroAmount amount={xmrAmount} />
|
<PiconeroAmount amount={xmrAmount} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HistoryRow({ swap }: HistoryRowProps) {
|
export default function HistoryRow(swap: GetSwapInfoResponse) {
|
||||||
const btcAmount = getSwapBtcAmount(swap);
|
|
||||||
const xmrAmount = getSwapXmrAmount(swap);
|
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -64,13 +52,16 @@ export default function HistoryRow({ swap }: HistoryRowProps) {
|
||||||
{expanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
|
{expanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{swap.swap_id.substring(0, 5)}...</TableCell>
|
<TableCell>{swap.swap_id}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<AmountTransfer xmrAmount={xmrAmount} btcAmount={btcAmount} />
|
<AmountTransfer
|
||||||
|
xmrAmount={swap.xmr_amount}
|
||||||
|
btcAmount={swap.btc_amount}
|
||||||
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{getHumanReadableDbStateType(swap.state_name)}</TableCell>
|
<TableCell>{swap.state_name.toString()}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<HistoryRowActions swap={swap} />
|
<HistoryRowActions {...swap} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
|
|
|
@ -1,68 +1,65 @@
|
||||||
import { Tooltip } from "@material-ui/core";
|
import { Tooltip } from "@material-ui/core";
|
||||||
import Button, { ButtonProps } from "@material-ui/core/Button/Button";
|
import { ButtonProps } from "@material-ui/core/Button/Button";
|
||||||
|
import { green, red } from "@material-ui/core/colors";
|
||||||
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 PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
||||||
import IpcInvokeButton from "../../../IpcInvokeButton";
|
import { GetSwapInfoResponse } from "models/tauriModel";
|
||||||
import {
|
import {
|
||||||
GetSwapInfoResponse,
|
BobStateName,
|
||||||
SwapStateName,
|
GetSwapInfoResponseExt,
|
||||||
isSwapStateNamePossiblyCancellableSwap,
|
isBobStateNamePossiblyCancellableSwap,
|
||||||
isSwapStateNamePossiblyRefundableSwap,
|
isBobStateNamePossiblyRefundableSwap,
|
||||||
} from "../../../../../models/rpcModel";
|
} from "models/tauriModelExt";
|
||||||
|
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||||
|
import { resumeSwap } from "renderer/rpc";
|
||||||
|
|
||||||
export function SwapResumeButton({
|
export function SwapResumeButton({
|
||||||
swap,
|
swap,
|
||||||
...props
|
...props
|
||||||
}: { swap: GetSwapInfoResponse } & ButtonProps) {
|
}: ButtonProps & { swap: GetSwapInfoResponse }) {
|
||||||
return (
|
return (
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={swap.completed}
|
disabled={swap.completed}
|
||||||
ipcChannel="spawn-resume-swap"
|
|
||||||
ipcArgs={[swap.swap_id]}
|
|
||||||
endIcon={<PlayArrowIcon />}
|
endIcon={<PlayArrowIcon />}
|
||||||
requiresRpc
|
onClick={() => resumeSwap(swap.swap_id)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
Resume
|
Resume
|
||||||
</IpcInvokeButton>
|
</PromiseInvokeButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SwapCancelRefundButton({
|
export function SwapCancelRefundButton({
|
||||||
swap,
|
swap,
|
||||||
...props
|
...props
|
||||||
}: { swap: GetSwapInfoResponse } & ButtonProps) {
|
}: { swap: GetSwapInfoResponseExt } & ButtonProps) {
|
||||||
const cancelOrRefundable =
|
const cancelOrRefundable =
|
||||||
isSwapStateNamePossiblyCancellableSwap(swap.state_name) ||
|
isBobStateNamePossiblyCancellableSwap(swap.state_name) ||
|
||||||
isSwapStateNamePossiblyRefundableSwap(swap.state_name);
|
isBobStateNamePossiblyRefundableSwap(swap.state_name);
|
||||||
|
|
||||||
if (!cancelOrRefundable) {
|
if (!cancelOrRefundable) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
ipcChannel="spawn-cancel-refund"
|
|
||||||
ipcArgs={[swap.swap_id]}
|
|
||||||
requiresRpc
|
|
||||||
displayErrorSnackbar={false}
|
displayErrorSnackbar={false}
|
||||||
{...props}
|
{...props}
|
||||||
|
onClick={async () => {
|
||||||
|
// TODO: Implement this using the Tauri RPC
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Attempt manual Cancel & Refund
|
Attempt manual Cancel & Refund
|
||||||
</IpcInvokeButton>
|
</PromiseInvokeButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HistoryRowActions({
|
export default function HistoryRowActions(swap: GetSwapInfoResponse) {
|
||||||
swap,
|
if (swap.state_name === BobStateName.XmrRedeemed) {
|
||||||
}: {
|
|
||||||
swap: GetSwapInfoResponse;
|
|
||||||
}) {
|
|
||||||
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] }} />
|
||||||
|
@ -70,7 +67,7 @@ export default function HistoryRowActions({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (swap.state_name === SwapStateName.BtcRefunded) {
|
if (swap.state_name === BobStateName.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] }} />
|
||||||
|
@ -78,7 +75,9 @@ export default function HistoryRowActions({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (swap.state_name === SwapStateName.BtcPunished) {
|
// TODO: Display a button here to attempt a cooperative redeem
|
||||||
|
// See this PR: https://github.com/UnstoppableSwap/unstoppableswap-gui/pull/212
|
||||||
|
if (swap.state_name === BobStateName.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] }} />
|
||||||
|
|
|
@ -8,24 +8,15 @@ import {
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
|
import { GetSwapInfoResponse } from "models/tauriModel";
|
||||||
import { isTestnet } from "store/config";
|
|
||||||
import {
|
import {
|
||||||
getHumanReadableDbStateType,
|
|
||||||
getSwapBtcAmount,
|
|
||||||
getSwapExchangeRate,
|
|
||||||
getSwapTxFees,
|
|
||||||
getSwapXmrAmount,
|
|
||||||
GetSwapInfoResponse,
|
|
||||||
} from "../../../../../models/rpcModel";
|
|
||||||
import SwapLogFileOpenButton from "./SwapLogFileOpenButton";
|
|
||||||
import { SwapCancelRefundButton } from "./HistoryRowActions";
|
|
||||||
import { SwapMoneroRecoveryButton } from "./SwapMoneroRecoveryButton";
|
|
||||||
import {
|
|
||||||
BitcoinAmount,
|
|
||||||
MoneroAmount,
|
|
||||||
MoneroBitcoinExchangeRate,
|
MoneroBitcoinExchangeRate,
|
||||||
|
PiconeroAmount,
|
||||||
|
SatsAmount,
|
||||||
} from "renderer/components/other/Units";
|
} from "renderer/components/other/Units";
|
||||||
|
import { isTestnet } from "store/config";
|
||||||
|
import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
|
||||||
|
import SwapLogFileOpenButton from "./SwapLogFileOpenButton";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
outer: {
|
outer: {
|
||||||
|
@ -47,12 +38,6 @@ export default function HistoryRowExpanded({
|
||||||
}) {
|
}) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const { seller, start_date: startDate } = swap;
|
|
||||||
const btcAmount = getSwapBtcAmount(swap);
|
|
||||||
const xmrAmount = getSwapXmrAmount(swap);
|
|
||||||
const txFees = getSwapTxFees(swap);
|
|
||||||
const exchangeRate = getSwapExchangeRate(swap);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={classes.outer}>
|
<Box className={classes.outer}>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
|
@ -60,7 +45,7 @@ export default function HistoryRowExpanded({
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Started on</TableCell>
|
<TableCell>Started on</TableCell>
|
||||||
<TableCell>{startDate}</TableCell>
|
<TableCell>{swap.start_date}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Swap ID</TableCell>
|
<TableCell>Swap ID</TableCell>
|
||||||
|
@ -68,38 +53,39 @@ export default function HistoryRowExpanded({
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>State Name</TableCell>
|
<TableCell>State Name</TableCell>
|
||||||
<TableCell>
|
<TableCell>{swap.state_name}</TableCell>
|
||||||
{getHumanReadableDbStateType(swap.state_name)}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Monero Amount</TableCell>
|
<TableCell>Monero Amount</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<MoneroAmount amount={xmrAmount} />
|
<PiconeroAmount amount={swap.xmr_amount} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Bitcoin Amount</TableCell>
|
<TableCell>Bitcoin Amount</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<BitcoinAmount amount={btcAmount} />
|
<SatsAmount amount={swap.btc_amount} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Exchange Rate</TableCell>
|
<TableCell>Exchange Rate</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<MoneroBitcoinExchangeRate rate={exchangeRate} />
|
<MoneroBitcoinExchangeRate
|
||||||
|
satsAmount={swap.btc_amount}
|
||||||
|
piconeroAmount={swap.xmr_amount}
|
||||||
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Bitcoin Network Fees</TableCell>
|
<TableCell>Bitcoin Network Fees</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<BitcoinAmount amount={txFees} />
|
<SatsAmount amount={swap.tx_lock_fee} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Provider Address</TableCell>
|
<TableCell>Provider Address</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Box>{seller.addresses.join(", ")}</Box>
|
<Box>{swap.seller.addresses.join(", ")}</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
@ -122,12 +108,16 @@ export default function HistoryRowExpanded({
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
|
{/*
|
||||||
|
// TOOD: reimplement these buttons using Tauri
|
||||||
|
|
||||||
<SwapCancelRefundButton swap={swap} variant="contained" size="small" />
|
<SwapCancelRefundButton swap={swap} variant="contained" size="small" />
|
||||||
<SwapMoneroRecoveryButton
|
<SwapMoneroRecoveryButton
|
||||||
swap={swap}
|
swap={swap}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
|
*/}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,12 +9,7 @@ import {
|
||||||
TableHead,
|
TableHead,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { sortBy } from "lodash";
|
import { useSwapInfosSortedByDate } from "../../../../../store/hooks";
|
||||||
import { parseDateString } from "utils/parseUtils";
|
|
||||||
import {
|
|
||||||
useAppSelector,
|
|
||||||
useSwapInfosSortedByDate,
|
|
||||||
} from "../../../../../store/hooks";
|
|
||||||
import HistoryRow from "./HistoryRow";
|
import HistoryRow from "./HistoryRow";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
@ -43,7 +38,7 @@ export default function HistoryTable() {
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{swapSortedByDate.map((swap) => (
|
{swapSortedByDate.map((swap) => (
|
||||||
<HistoryRow swap={swap} key={swap.swap_id} />
|
<HistoryRow {...swap} key={swap.swap_id} />
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { ButtonProps } from "@material-ui/core/Button/Button";
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
|
@ -6,9 +5,10 @@ import {
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { useState } from "react";
|
import { ButtonProps } from "@material-ui/core/Button/Button";
|
||||||
import { CliLog } from "models/cliModel";
|
import { CliLog } from "models/cliModel";
|
||||||
import IpcInvokeButton from "../../../IpcInvokeButton";
|
import { useState } from "react";
|
||||||
|
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||||
import CliLogsBox from "../../../other/RenderedCliLog";
|
import CliLogsBox from "../../../other/RenderedCliLog";
|
||||||
|
|
||||||
export default function SwapLogFileOpenButton({
|
export default function SwapLogFileOpenButton({
|
||||||
|
@ -19,16 +19,17 @@ export default function SwapLogFileOpenButton({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
ipcArgs={[swapId]}
|
|
||||||
ipcChannel="get-swap-logs"
|
|
||||||
onSuccess={(data) => {
|
onSuccess={(data) => {
|
||||||
setLogs(data as CliLog[]);
|
setLogs(data as CliLog[]);
|
||||||
}}
|
}}
|
||||||
|
onClick={async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
view log
|
View log
|
||||||
</IpcInvokeButton>
|
</PromiseInvokeButton>
|
||||||
{logs && (
|
{logs && (
|
||||||
<Dialog open onClose={() => setLogs(null)} fullWidth maxWidth="lg">
|
<Dialog open onClose={() => setLogs(null)} fullWidth maxWidth="lg">
|
||||||
<DialogTitle>Logs of swap {swapId}</DialogTitle>
|
<DialogTitle>Logs of swap {swapId}</DialogTitle>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { ButtonProps } from "@material-ui/core/Button/Button";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
|
@ -8,17 +7,17 @@ import {
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
Link,
|
Link,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { useAppDispatch, useAppSelector } from "store/hooks";
|
import { ButtonProps } from "@material-ui/core/Button/Button";
|
||||||
|
import { GetSwapInfoArgs } from "models/tauriModel";
|
||||||
import { rpcResetMoneroRecoveryKeys } from "store/features/rpcSlice";
|
import { rpcResetMoneroRecoveryKeys } from "store/features/rpcSlice";
|
||||||
import {
|
import { useAppDispatch, useAppSelector } from "store/hooks";
|
||||||
GetSwapInfoResponse,
|
|
||||||
isSwapMoneroRecoverable,
|
|
||||||
} from "../../../../../models/rpcModel";
|
|
||||||
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() {
|
||||||
|
// TODO: Reimplement this using the new Tauri API
|
||||||
|
return null;
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const keys = useAppSelector((s) => s.rpc.state.moneroRecovery);
|
const keys = useAppSelector((s) => s.rpc.state.moneroRecovery);
|
||||||
|
|
||||||
|
@ -96,24 +95,28 @@ function MoneroRecoveryKeysDialog({ swap }: { swap: GetSwapInfoResponse }) {
|
||||||
export function SwapMoneroRecoveryButton({
|
export function SwapMoneroRecoveryButton({
|
||||||
swap,
|
swap,
|
||||||
...props
|
...props
|
||||||
}: { swap: GetSwapInfoResponse } & ButtonProps) {
|
}: { swap: GetSwapInfoArgs } & ButtonProps) {
|
||||||
|
return <> </>;
|
||||||
|
/* TODO: Reimplement this using the new Tauri API
|
||||||
const isRecoverable = isSwapMoneroRecoverable(swap.state_name);
|
const isRecoverable = isSwapMoneroRecoverable(swap.state_name);
|
||||||
|
|
||||||
|
|
||||||
if (!isRecoverable) {
|
if (!isRecoverable) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IpcInvokeButton
|
<PromiseInvokeButton
|
||||||
ipcChannel="spawn-monero-recovery"
|
onClick={async () => {
|
||||||
ipcArgs={[swap.swap_id]}
|
throw new Error("Not implemented");
|
||||||
requiresRpc
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
Display Monero Recovery Keys
|
Display Monero Recovery Keys
|
||||||
</IpcInvokeButton>
|
</PromiseInvokeButton>
|
||||||
<MoneroRecoveryKeysDialog swap={swap} />
|
<MoneroRecoveryKeysDialog swap={swap} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
import { ChangeEvent, useEffect, useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
makeStyles,
|
|
||||||
Box,
|
Box,
|
||||||
Paper,
|
|
||||||
Typography,
|
|
||||||
TextField,
|
|
||||||
LinearProgress,
|
|
||||||
Fab,
|
Fab,
|
||||||
|
LinearProgress,
|
||||||
|
makeStyles,
|
||||||
|
Paper,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||||
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
|
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
|
||||||
import SwapHorizIcon from "@material-ui/icons/SwapHoriz";
|
import SwapHorizIcon from "@material-ui/icons/SwapHoriz";
|
||||||
import { Alert } from "@material-ui/lab";
|
import { Alert } from "@material-ui/lab";
|
||||||
import { satsToBtc } from "utils/conversionUtils";
|
|
||||||
import { useAppSelector } from "store/hooks";
|
|
||||||
import { ExtendedProviderStatus } from "models/apiModel";
|
import { ExtendedProviderStatus } from "models/apiModel";
|
||||||
import { isSwapState } from "models/storeModel";
|
import { ChangeEvent, useEffect, useState } from "react";
|
||||||
import SwapDialog from "../../modal/swap/SwapDialog";
|
import { useAppSelector } from "store/hooks";
|
||||||
import ProviderSelect from "../../modal/provider/ProviderSelect";
|
import { satsToBtc } from "utils/conversionUtils";
|
||||||
import {
|
import {
|
||||||
ListSellersDialogOpenButton,
|
ListSellersDialogOpenButton,
|
||||||
ProviderSubmitDialogOpenButton,
|
ProviderSubmitDialogOpenButton,
|
||||||
} from "../../modal/provider/ProviderListDialog";
|
} from "../../modal/provider/ProviderListDialog";
|
||||||
|
import ProviderSelect from "../../modal/provider/ProviderSelect";
|
||||||
|
import SwapDialog from "../../modal/swap/SwapDialog";
|
||||||
|
|
||||||
// After RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN failed reconnection attempts we can assume the public registry is down
|
// After RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN failed reconnection attempts we can assume the public registry is down
|
||||||
const RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN = 1;
|
const RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN = 1;
|
||||||
|
@ -84,9 +83,7 @@ function HasProviderSwapWidget({
|
||||||
}) {
|
}) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const forceShowDialog = useAppSelector((state) =>
|
const forceShowDialog = useAppSelector((state) => state.swap.state !== null);
|
||||||
isSwapState(state.swap.state),
|
|
||||||
);
|
|
||||||
const [showDialog, setShowDialog] = useState(false);
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
const [btcFieldValue, setBtcFieldValue] = useState<number | string>(
|
const [btcFieldValue, setBtcFieldValue] = useState<number | string>(
|
||||||
satsToBtc(selectedProvider.minSwapAmount),
|
satsToBtc(selectedProvider.minSwapAmount),
|
||||||
|
@ -177,9 +174,7 @@ function HasProviderSwapWidget({
|
||||||
}
|
}
|
||||||
|
|
||||||
function HasNoProvidersSwapWidget() {
|
function HasNoProvidersSwapWidget() {
|
||||||
const forceShowDialog = useAppSelector((state) =>
|
const forceShowDialog = useAppSelector((state) => state.swap.state !== null);
|
||||||
isSwapState(state.swap.state),
|
|
||||||
);
|
|
||||||
const isPublicRegistryDown = useAppSelector((state) =>
|
const isPublicRegistryDown = useAppSelector((state) =>
|
||||||
isRegistryDown(
|
isRegistryDown(
|
||||||
state.providers.registry.failedReconnectAttemptsSinceLastSuccess,
|
state.providers.registry.failedReconnectAttemptsSinceLastSuccess,
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { Button, CircularProgress, IconButton } from "@material-ui/core";
|
|
||||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||||
import IpcInvokeButton from "../../IpcInvokeButton";
|
|
||||||
import { checkBitcoinBalance } from "renderer/rpc";
|
|
||||||
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||||
|
import { checkBitcoinBalance } from "renderer/rpc";
|
||||||
|
|
||||||
export default function WalletRefreshButton() {
|
export default function WalletRefreshButton() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { render } from "react-dom";
|
import { createRoot } from "react-dom/client";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { setAlerts } from "store/features/alertsSlice";
|
import { setAlerts } from "store/features/alertsSlice";
|
||||||
import { setRegistryProviders } from "store/features/providersSlice";
|
import { setRegistryProviders } from "store/features/providersSlice";
|
||||||
|
@ -14,16 +14,17 @@ import App from "./components/App";
|
||||||
import { checkBitcoinBalance, getRawSwapInfos } from "./rpc";
|
import { checkBitcoinBalance, getRawSwapInfos } from "./rpc";
|
||||||
import { store } from "./store/storeRenderer";
|
import { store } from "./store/storeRenderer";
|
||||||
|
|
||||||
setTimeout(() => {
|
setInterval(() => {
|
||||||
checkBitcoinBalance();
|
checkBitcoinBalance();
|
||||||
getRawSwapInfos();
|
getRawSwapInfos();
|
||||||
}, 10000);
|
}, 5000);
|
||||||
|
|
||||||
render(
|
const container = document.getElementById("root");
|
||||||
|
const root = createRoot(container!);
|
||||||
|
root.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<App />
|
<App />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
document.getElementById("root"),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
async function fetchInitialData() {
|
async function fetchInitialData() {
|
||||||
|
|
|
@ -1,31 +1,89 @@
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke as invokeUnsafe } from "@tauri-apps/api/core";
|
||||||
import { store } from "./store/storeRenderer";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import {
|
||||||
|
BalanceArgs,
|
||||||
|
BalanceResponse,
|
||||||
|
BuyXmrArgs,
|
||||||
|
BuyXmrResponse,
|
||||||
|
GetSwapInfoResponse,
|
||||||
|
ResumeSwapArgs,
|
||||||
|
ResumeSwapResponse,
|
||||||
|
SuspendCurrentSwapResponse,
|
||||||
|
TauriSwapProgressEventWrapper,
|
||||||
|
WithdrawBtcArgs,
|
||||||
|
WithdrawBtcResponse,
|
||||||
|
} from "models/tauriModel";
|
||||||
import { rpcSetBalance, rpcSetSwapInfo } from "store/features/rpcSlice";
|
import { rpcSetBalance, rpcSetSwapInfo } from "store/features/rpcSlice";
|
||||||
|
import { swapTauriEventReceived } from "store/features/swapSlice";
|
||||||
|
import { store } from "./store/storeRenderer";
|
||||||
|
import { Provider } from "models/apiModel";
|
||||||
|
import { providerToConcatenatedMultiAddr } from "utils/multiAddrUtils";
|
||||||
|
|
||||||
|
listen<TauriSwapProgressEventWrapper>("swap-progress-update", (event) => {
|
||||||
|
console.log("Received swap progress event", event.payload);
|
||||||
|
store.dispatch(swapTauriEventReceived(event.payload));
|
||||||
|
});
|
||||||
|
|
||||||
|
async function invoke<ARGS, RESPONSE>(
|
||||||
|
command: string,
|
||||||
|
args: ARGS,
|
||||||
|
): Promise<RESPONSE> {
|
||||||
|
return invokeUnsafe(command, {
|
||||||
|
args: args as Record<string, unknown>,
|
||||||
|
}) as Promise<RESPONSE>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function invokeNoArgs<RESPONSE>(command: string): Promise<RESPONSE> {
|
||||||
|
return invokeUnsafe(command, {}) as Promise<RESPONSE>;
|
||||||
|
}
|
||||||
|
|
||||||
export async function checkBitcoinBalance() {
|
export async function checkBitcoinBalance() {
|
||||||
const response = (await invoke("get_balance")) as {
|
const response = await invoke<BalanceArgs, BalanceResponse>("get_balance", {
|
||||||
balance: number;
|
force_refresh: true,
|
||||||
};
|
});
|
||||||
|
|
||||||
store.dispatch(rpcSetBalance(response.balance));
|
store.dispatch(rpcSetBalance(response.balance));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRawSwapInfos() {
|
export async function getRawSwapInfos() {
|
||||||
const response = await invoke("get_swap_infos_all");
|
const response =
|
||||||
|
await invokeNoArgs<GetSwapInfoResponse[]>("get_swap_infos_all");
|
||||||
|
|
||||||
(response as any[]).forEach((info) => store.dispatch(rpcSetSwapInfo(info)));
|
response.forEach((swapInfo) => {
|
||||||
|
store.dispatch(rpcSetSwapInfo(swapInfo));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function withdrawBtc(address: string): Promise<string> {
|
export async function withdrawBtc(address: string): Promise<string> {
|
||||||
const response = (await invoke("withdraw_btc", {
|
const response = await invoke<WithdrawBtcArgs, WithdrawBtcResponse>(
|
||||||
args: {
|
"withdraw_btc",
|
||||||
|
{
|
||||||
address,
|
address,
|
||||||
amount: null,
|
amount: null,
|
||||||
},
|
},
|
||||||
})) as {
|
);
|
||||||
txid: string;
|
|
||||||
amount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
return response.txid;
|
return response.txid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function buyXmr(
|
||||||
|
seller: Provider,
|
||||||
|
bitcoin_change_address: string,
|
||||||
|
monero_receive_address: string,
|
||||||
|
) {
|
||||||
|
await invoke<BuyXmrArgs, BuyXmrResponse>("buy_xmr", {
|
||||||
|
seller: providerToConcatenatedMultiAddr(seller),
|
||||||
|
bitcoin_change_address,
|
||||||
|
monero_receive_address,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resumeSwap(swapId: string) {
|
||||||
|
await invoke<ResumeSwapArgs, ResumeSwapResponse>("resume_swap", {
|
||||||
|
swap_id: swapId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function suspendCurrentSwap() {
|
||||||
|
await invokeNoArgs<SuspendCurrentSwapResponse>("suspend_current_swap");
|
||||||
|
}
|
||||||
|
|
|
@ -2,14 +2,8 @@ import { ExtendedProviderStatus } from "models/apiModel";
|
||||||
|
|
||||||
export const isTestnet = () => true;
|
export const isTestnet = () => true;
|
||||||
|
|
||||||
export const isExternalRpc = () => true;
|
|
||||||
|
|
||||||
export const isDevelopment = true;
|
export const isDevelopment = true;
|
||||||
|
|
||||||
export function getStubTestnetProvider(): ExtendedProviderStatus | null {
|
export function getStubTestnetProvider(): ExtendedProviderStatus | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPlatform = () => {
|
|
||||||
return "mac";
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
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 { sortProviderList } from "utils/sortUtils";
|
|
||||||
import { isProviderCompatible } from "utils/multiAddrUtils";
|
|
||||||
import { getStubTestnetProvider } from "store/config";
|
import { getStubTestnetProvider } from "store/config";
|
||||||
|
import { isProviderCompatible } from "utils/multiAddrUtils";
|
||||||
|
import { sortProviderList } from "utils/sortUtils";
|
||||||
|
|
||||||
const stubTestnetProvider = getStubTestnetProvider();
|
const stubTestnetProvider = getStubTestnetProvider();
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,12 @@
|
||||||
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 { GetSwapInfoResponse } from "models/tauriModel";
|
||||||
|
import { CliLog } from "../../models/cliModel";
|
||||||
import {
|
import {
|
||||||
GetSwapInfoResponse,
|
|
||||||
MoneroRecoveryResponse,
|
MoneroRecoveryResponse,
|
||||||
RpcProcessStateType,
|
RpcProcessStateType,
|
||||||
} from "../../models/rpcModel";
|
} from "../../models/rpcModel";
|
||||||
import {
|
import { GetSwapInfoResponseExt } from "models/tauriModelExt";
|
||||||
CliLog,
|
|
||||||
isCliLog,
|
|
||||||
isCliLogDownloadingMoneroWalletRpc,
|
|
||||||
isCliLogFailedToSyncMoneroWallet,
|
|
||||||
isCliLogFinishedSyncingMoneroWallet,
|
|
||||||
isCliLogStartedRpcServer,
|
|
||||||
isCliLogStartedSyncingMoneroWallet,
|
|
||||||
} from "../../models/cliModel";
|
|
||||||
import { getLogsAndStringsFromRawFileString } from "utils/parseUtils";
|
|
||||||
|
|
||||||
type Process =
|
type Process =
|
||||||
| {
|
| {
|
||||||
|
@ -41,7 +32,7 @@ interface State {
|
||||||
withdrawTxId: string | null;
|
withdrawTxId: string | null;
|
||||||
rendezvous_discovered_sellers: (ExtendedProviderStatus | ProviderStatus)[];
|
rendezvous_discovered_sellers: (ExtendedProviderStatus | ProviderStatus)[];
|
||||||
swapInfos: {
|
swapInfos: {
|
||||||
[swapId: string]: GetSwapInfoResponse;
|
[swapId: string]: GetSwapInfoResponseExt;
|
||||||
};
|
};
|
||||||
moneroRecovery: {
|
moneroRecovery: {
|
||||||
swapId: string;
|
swapId: string;
|
||||||
|
@ -51,7 +42,8 @@ interface State {
|
||||||
isSyncing: boolean;
|
isSyncing: boolean;
|
||||||
};
|
};
|
||||||
moneroWalletRpc: {
|
moneroWalletRpc: {
|
||||||
updateState: false | MoneroWalletRpcUpdateState;
|
// TODO: Reimplement this using Tauri
|
||||||
|
updateState: false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,44 +77,6 @@ export const rpcSlice = createSlice({
|
||||||
name: "rpc",
|
name: "rpc",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
rpcAddLogs(slice, action: PayloadAction<(CliLog | string)[]>) {
|
|
||||||
if (
|
|
||||||
slice.process.type === RpcProcessStateType.STARTED ||
|
|
||||||
slice.process.type === RpcProcessStateType.LISTENING_FOR_CONNECTIONS ||
|
|
||||||
slice.process.type === RpcProcessStateType.EXITED
|
|
||||||
) {
|
|
||||||
const logs = action.payload;
|
|
||||||
slice.process.logs.push(...logs);
|
|
||||||
|
|
||||||
logs.filter(isCliLog).forEach((log) => {
|
|
||||||
if (
|
|
||||||
isCliLogStartedRpcServer(log) &&
|
|
||||||
slice.process.type === RpcProcessStateType.STARTED
|
|
||||||
) {
|
|
||||||
slice.process = {
|
|
||||||
type: RpcProcessStateType.LISTENING_FOR_CONNECTIONS,
|
|
||||||
logs: slice.process.logs,
|
|
||||||
address: log.fields.addr,
|
|
||||||
};
|
|
||||||
} else if (isCliLogDownloadingMoneroWalletRpc(log)) {
|
|
||||||
slice.state.moneroWalletRpc.updateState = {
|
|
||||||
progress: log.fields.progress,
|
|
||||||
downloadUrl: log.fields.download_url,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (log.fields.progress === "100%") {
|
|
||||||
slice.state.moneroWalletRpc.updateState = false;
|
|
||||||
}
|
|
||||||
} else if (isCliLogStartedSyncingMoneroWallet(log)) {
|
|
||||||
slice.state.moneroWallet.isSyncing = true;
|
|
||||||
} else if (isCliLogFinishedSyncingMoneroWallet(log)) {
|
|
||||||
slice.state.moneroWallet.isSyncing = false;
|
|
||||||
} else if (isCliLogFailedToSyncMoneroWallet(log)) {
|
|
||||||
slice.state.moneroWallet.isSyncing = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rpcInitiate(slice) {
|
rpcInitiate(slice) {
|
||||||
slice.process = {
|
slice.process = {
|
||||||
type: RpcProcessStateType.STARTED,
|
type: RpcProcessStateType.STARTED,
|
||||||
|
@ -169,7 +123,8 @@ 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.swap_id] = action.payload;
|
slice.state.swapInfos[action.payload.swap_id] =
|
||||||
|
action.payload as GetSwapInfoResponseExt;
|
||||||
},
|
},
|
||||||
rpcSetEndpointBusy(slice, action: PayloadAction<string>) {
|
rpcSetEndpointBusy(slice, action: PayloadAction<string>) {
|
||||||
if (!slice.busyEndpoints.includes(action.payload)) {
|
if (!slice.busyEndpoints.includes(action.payload)) {
|
||||||
|
@ -202,7 +157,6 @@ export const rpcSlice = createSlice({
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
rpcProcessExited,
|
rpcProcessExited,
|
||||||
rpcAddLogs,
|
|
||||||
rpcInitiate,
|
rpcInitiate,
|
||||||
rpcSetBalance,
|
rpcSetBalance,
|
||||||
rpcSetWithdrawTxId,
|
rpcSetWithdrawTxId,
|
||||||
|
|
|
@ -1,55 +1,12 @@
|
||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { extractAmountFromUnitString } from "utils/parseUtils";
|
import { TauriSwapProgressEventWrapper } from "models/tauriModel";
|
||||||
import { Provider } from "models/apiModel";
|
import { SwapSlice } from "../../models/storeModel";
|
||||||
import {
|
|
||||||
isSwapStateBtcLockInMempool,
|
|
||||||
isSwapStateProcessExited,
|
|
||||||
isSwapStateXmrLockInMempool,
|
|
||||||
SwapSlice,
|
|
||||||
SwapStateAttemptingCooperativeRedeeem,
|
|
||||||
SwapStateBtcCancelled,
|
|
||||||
SwapStateBtcLockInMempool,
|
|
||||||
SwapStateBtcPunished,
|
|
||||||
SwapStateBtcRedemeed,
|
|
||||||
SwapStateBtcRefunded,
|
|
||||||
SwapStateInitiated,
|
|
||||||
SwapStateProcessExited,
|
|
||||||
SwapStateReceivedQuote,
|
|
||||||
SwapStateStarted,
|
|
||||||
SwapStateType,
|
|
||||||
SwapStateWaitingForBtcDeposit,
|
|
||||||
SwapStateXmrLocked,
|
|
||||||
SwapStateXmrLockInMempool,
|
|
||||||
SwapStateXmrRedeemInMempool,
|
|
||||||
} from "../../models/storeModel";
|
|
||||||
import {
|
|
||||||
isCliLogAliceLockedXmr,
|
|
||||||
isCliLogBtcTxStatusChanged,
|
|
||||||
isCliLogPublishedBtcTx,
|
|
||||||
isCliLogReceivedQuote,
|
|
||||||
isCliLogReceivedXmrLockTxConfirmation,
|
|
||||||
isCliLogRedeemedXmr,
|
|
||||||
isCliLogStartedSwap,
|
|
||||||
isCliLogWaitingForBtcDeposit,
|
|
||||||
CliLog,
|
|
||||||
isCliLogAdvancingState,
|
|
||||||
SwapSpawnType,
|
|
||||||
isCliLogBtcTxFound,
|
|
||||||
isCliLogReleasingSwapLockLog,
|
|
||||||
isYouHaveBeenPunishedCliLog,
|
|
||||||
isCliLogAcquiringSwapLockLog,
|
|
||||||
isCliLogApiCallError,
|
|
||||||
isCliLogDeterminedSwapAmount,
|
|
||||||
isCliLogAttemptingToCooperativelyRedeemXmr,
|
|
||||||
} from "../../models/cliModel";
|
|
||||||
import logger from "../../utils/logger";
|
|
||||||
|
|
||||||
const initialState: SwapSlice = {
|
const initialState: SwapSlice = {
|
||||||
state: null,
|
state: null,
|
||||||
processRunning: false,
|
|
||||||
swapId: null,
|
|
||||||
logs: [],
|
logs: [],
|
||||||
provider: null,
|
|
||||||
|
// TODO: Remove this and replace logic entirely with Tauri events
|
||||||
spawnType: null,
|
spawnType: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -57,266 +14,27 @@ export const swapSlice = createSlice({
|
||||||
name: "swap",
|
name: "swap",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
swapAddLog(
|
swapTauriEventReceived(
|
||||||
slice,
|
swap,
|
||||||
action: PayloadAction<{ logs: CliLog[]; isFromRestore: boolean }>,
|
action: PayloadAction<TauriSwapProgressEventWrapper>,
|
||||||
) {
|
) {
|
||||||
const { logs } = action.payload;
|
if (swap.state === null || action.payload.swap_id !== swap.state.swapId) {
|
||||||
slice.logs.push(...logs);
|
swap.state = {
|
||||||
|
curr: action.payload.event,
|
||||||
logs.forEach((log) => {
|
prev: null,
|
||||||
if (
|
swapId: action.payload.swap_id,
|
||||||
isCliLogAcquiringSwapLockLog(log) &&
|
|
||||||
!action.payload.isFromRestore
|
|
||||||
) {
|
|
||||||
slice.processRunning = true;
|
|
||||||
slice.swapId = log.fields.swap_id;
|
|
||||||
// TODO: Maybe we can infer more info here (state) from the log
|
|
||||||
} else if (isCliLogReceivedQuote(log)) {
|
|
||||||
const price = extractAmountFromUnitString(log.fields.price);
|
|
||||||
const minimumSwapAmount = extractAmountFromUnitString(
|
|
||||||
log.fields.minimum_amount,
|
|
||||||
);
|
|
||||||
const maximumSwapAmount = extractAmountFromUnitString(
|
|
||||||
log.fields.maximum_amount,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
price != null &&
|
|
||||||
minimumSwapAmount != null &&
|
|
||||||
maximumSwapAmount != null
|
|
||||||
) {
|
|
||||||
const nextState: SwapStateReceivedQuote = {
|
|
||||||
type: SwapStateType.RECEIVED_QUOTE,
|
|
||||||
price,
|
|
||||||
minimumSwapAmount,
|
|
||||||
maximumSwapAmount,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
}
|
|
||||||
} else if (isCliLogWaitingForBtcDeposit(log)) {
|
|
||||||
const maxGiveable = extractAmountFromUnitString(
|
|
||||||
log.fields.max_giveable,
|
|
||||||
);
|
|
||||||
const minDeposit = extractAmountFromUnitString(
|
|
||||||
log.fields.min_deposit_until_swap_will_start,
|
|
||||||
);
|
|
||||||
const maxDeposit = extractAmountFromUnitString(
|
|
||||||
log.fields.max_deposit_until_maximum_amount_is_reached,
|
|
||||||
);
|
|
||||||
const minimumAmount = extractAmountFromUnitString(
|
|
||||||
log.fields.minimum_amount,
|
|
||||||
);
|
|
||||||
const maximumAmount = extractAmountFromUnitString(
|
|
||||||
log.fields.maximum_amount,
|
|
||||||
);
|
|
||||||
const minBitcoinLockTxFee = extractAmountFromUnitString(
|
|
||||||
log.fields.min_bitcoin_lock_tx_fee,
|
|
||||||
);
|
|
||||||
const price = extractAmountFromUnitString(log.fields.price);
|
|
||||||
|
|
||||||
const depositAddress = log.fields.deposit_address;
|
|
||||||
|
|
||||||
if (
|
|
||||||
maxGiveable != null &&
|
|
||||||
minimumAmount != null &&
|
|
||||||
maximumAmount != null &&
|
|
||||||
minDeposit != null &&
|
|
||||||
maxDeposit != null &&
|
|
||||||
minBitcoinLockTxFee != null &&
|
|
||||||
price != null
|
|
||||||
) {
|
|
||||||
const nextState: SwapStateWaitingForBtcDeposit = {
|
|
||||||
type: SwapStateType.WAITING_FOR_BTC_DEPOSIT,
|
|
||||||
depositAddress,
|
|
||||||
maxGiveable,
|
|
||||||
minimumAmount,
|
|
||||||
maximumAmount,
|
|
||||||
minDeposit,
|
|
||||||
maxDeposit,
|
|
||||||
price,
|
|
||||||
minBitcoinLockTxFee,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
}
|
|
||||||
} else if (isCliLogDeterminedSwapAmount(log)) {
|
|
||||||
const amount = extractAmountFromUnitString(log.fields.amount);
|
|
||||||
const fees = extractAmountFromUnitString(log.fields.fees);
|
|
||||||
|
|
||||||
const nextState: SwapStateStarted = {
|
|
||||||
type: SwapStateType.STARTED,
|
|
||||||
txLockDetails:
|
|
||||||
amount != null && fees != null ? { amount, fees } : null,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
} else if (isCliLogStartedSwap(log)) {
|
|
||||||
if (slice.state?.type !== SwapStateType.STARTED) {
|
|
||||||
const nextState: SwapStateStarted = {
|
|
||||||
type: SwapStateType.STARTED,
|
|
||||||
txLockDetails: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
}
|
|
||||||
|
|
||||||
slice.swapId = log.fields.swap_id;
|
|
||||||
} else if (isCliLogPublishedBtcTx(log)) {
|
|
||||||
if (log.fields.kind === "lock") {
|
|
||||||
const nextState: SwapStateBtcLockInMempool = {
|
|
||||||
type: SwapStateType.BTC_LOCK_TX_IN_MEMPOOL,
|
|
||||||
bobBtcLockTxId: log.fields.txid,
|
|
||||||
bobBtcLockTxConfirmations: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
} else if (log.fields.kind === "cancel") {
|
|
||||||
const nextState: SwapStateBtcCancelled = {
|
|
||||||
type: SwapStateType.BTC_CANCELLED,
|
|
||||||
btcCancelTxId: log.fields.txid,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
} else if (log.fields.kind === "refund") {
|
|
||||||
const nextState: SwapStateBtcRefunded = {
|
|
||||||
type: SwapStateType.BTC_REFUNDED,
|
|
||||||
bobBtcRefundTxId: log.fields.txid,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
}
|
|
||||||
} else if (isCliLogBtcTxStatusChanged(log) || isCliLogBtcTxFound(log)) {
|
|
||||||
if (isSwapStateBtcLockInMempool(slice.state)) {
|
|
||||||
if (slice.state.bobBtcLockTxId === log.fields.txid) {
|
|
||||||
const newStatusText = isCliLogBtcTxStatusChanged(log)
|
|
||||||
? log.fields.new_status
|
|
||||||
: log.fields.status;
|
|
||||||
|
|
||||||
if (newStatusText.startsWith("confirmed with")) {
|
|
||||||
const confirmations = Number.parseInt(
|
|
||||||
newStatusText.split(" ")[2],
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
|
|
||||||
slice.state.bobBtcLockTxConfirmations = confirmations;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (isCliLogAliceLockedXmr(log)) {
|
|
||||||
const nextState: SwapStateXmrLockInMempool = {
|
|
||||||
type: SwapStateType.XMR_LOCK_TX_IN_MEMPOOL,
|
|
||||||
aliceXmrLockTxId: log.fields.txid,
|
|
||||||
aliceXmrLockTxConfirmations: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
} else if (isCliLogReceivedXmrLockTxConfirmation(log)) {
|
|
||||||
if (isSwapStateXmrLockInMempool(slice.state)) {
|
|
||||||
if (slice.state.aliceXmrLockTxId === log.fields.txid) {
|
|
||||||
slice.state.aliceXmrLockTxConfirmations = Number.parseInt(
|
|
||||||
log.fields.seen_confirmations,
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (isCliLogAdvancingState(log)) {
|
|
||||||
if (log.fields.state === "xmr is locked") {
|
|
||||||
const nextState: SwapStateXmrLocked = {
|
|
||||||
type: SwapStateType.XMR_LOCKED,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
} else if (log.fields.state === "btc is redeemed") {
|
|
||||||
const nextState: SwapStateBtcRedemeed = {
|
|
||||||
type: SwapStateType.BTC_REDEEMED,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
}
|
|
||||||
} else if (isCliLogRedeemedXmr(log)) {
|
|
||||||
const nextState: SwapStateXmrRedeemInMempool = {
|
|
||||||
type: SwapStateType.XMR_REDEEM_IN_MEMPOOL,
|
|
||||||
bobXmrRedeemTxId: log.fields.txid,
|
|
||||||
bobXmrRedeemAddress: log.fields.monero_receive_address,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
} else if (isYouHaveBeenPunishedCliLog(log)) {
|
|
||||||
const nextState: SwapStateBtcPunished = {
|
|
||||||
type: SwapStateType.BTC_PUNISHED,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
} else if (isCliLogAttemptingToCooperativelyRedeemXmr(log)) {
|
|
||||||
const nextState: SwapStateAttemptingCooperativeRedeeem = {
|
|
||||||
type: SwapStateType.ATTEMPTING_COOPERATIVE_REDEEM,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
} else if (
|
|
||||||
isCliLogReleasingSwapLockLog(log) &&
|
|
||||||
!action.payload.isFromRestore
|
|
||||||
) {
|
|
||||||
const nextState: SwapStateProcessExited = {
|
|
||||||
type: SwapStateType.PROCESS_EXITED,
|
|
||||||
prevState: slice.state,
|
|
||||||
rpcError: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
slice.state = nextState;
|
|
||||||
slice.processRunning = false;
|
|
||||||
} else if (isCliLogApiCallError(log) && !action.payload.isFromRestore) {
|
|
||||||
if (isSwapStateProcessExited(slice.state)) {
|
|
||||||
slice.state.rpcError = log.fields.err;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
logger.debug({ log }, `Swap log was not reduced`);
|
swap.state.prev = swap.state.curr;
|
||||||
|
swap.state.curr = action.payload.event;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
},
|
},
|
||||||
swapReset() {
|
swapReset() {
|
||||||
return initialState;
|
return initialState;
|
||||||
},
|
},
|
||||||
swapInitiate(
|
|
||||||
swap,
|
|
||||||
action: PayloadAction<{
|
|
||||||
provider: Provider | null;
|
|
||||||
spawnType: SwapSpawnType;
|
|
||||||
swapId: string | null;
|
|
||||||
}>,
|
|
||||||
) {
|
|
||||||
const nextState: SwapStateInitiated = {
|
|
||||||
type: SwapStateType.INITIATED,
|
|
||||||
};
|
|
||||||
|
|
||||||
swap.processRunning = true;
|
|
||||||
swap.state = nextState;
|
|
||||||
swap.logs = [];
|
|
||||||
swap.provider = action.payload.provider;
|
|
||||||
swap.spawnType = action.payload.spawnType;
|
|
||||||
swap.swapId = action.payload.swapId;
|
|
||||||
},
|
|
||||||
swapProcessExited(swap, action: PayloadAction<string | null>) {
|
|
||||||
if (!swap.processRunning) {
|
|
||||||
logger.warn(`swapProcessExited called on a swap that is not running`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextState: SwapStateProcessExited = {
|
|
||||||
type: SwapStateType.PROCESS_EXITED,
|
|
||||||
prevState: swap.state,
|
|
||||||
rpcError: action.payload,
|
|
||||||
};
|
|
||||||
|
|
||||||
swap.state = nextState;
|
|
||||||
swap.processRunning = false;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { swapInitiate, swapProcessExited, swapReset, swapAddLog } =
|
export const { swapReset, swapTauriEventReceived } = swapSlice.actions;
|
||||||
swapSlice.actions;
|
|
||||||
|
|
||||||
export default swapSlice.reducer;
|
export default swapSlice.reducer;
|
||||||
|
|
|
@ -17,17 +17,20 @@ export function useResumeableSwapsCount() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useIsSwapRunning() {
|
export function useIsSwapRunning() {
|
||||||
return useAppSelector((state) => state.swap.state !== null);
|
return useAppSelector(
|
||||||
|
(state) =>
|
||||||
|
state.swap.state !== null && state.swap.state.curr.type !== "Released",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useActiveSwapId() {
|
export function useActiveSwapId() {
|
||||||
return useAppSelector((s) => s.swap.swapId);
|
return useAppSelector((s) => s.swap.state?.swapId ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useActiveSwapInfo() {
|
export function useActiveSwapInfo() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { ExtendedProviderStatus, Provider } from "models/apiModel";
|
||||||
import { Multiaddr } from "multiaddr";
|
import { Multiaddr } from "multiaddr";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import { ExtendedProviderStatus, Provider } from "models/apiModel";
|
|
||||||
import { isTestnet } from "store/config";
|
import { isTestnet } from "store/config";
|
||||||
|
|
||||||
const MIN_ASB_VERSION = "0.12.0";
|
const MIN_ASB_VERSION = "0.12.0";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CliLog, isCliLog } from "models/cliModel";
|
import { CliLog } from "models/cliModel";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Extract btc amount from string
|
Extract btc amount from string
|
||||||
|
@ -17,21 +17,28 @@ export function extractAmountFromUnitString(text: string): number | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// E.g 2021-12-29 14:25:59.64082 +00:00:00
|
// E.g: 2024-08-19 6:11:37.475038 +00:00:00
|
||||||
export function parseDateString(str: string): number {
|
export function parseDateString(str: string): number {
|
||||||
const parts = str.split(" ").slice(0, -1);
|
// Split the string and take only the date and time parts
|
||||||
if (parts.length !== 2) {
|
const [datePart, timePart] = str.split(" ");
|
||||||
throw new Error(
|
|
||||||
`Date string does not consist solely of date and time Str: ${str} Parts: ${parts}`,
|
if (!datePart || !timePart) {
|
||||||
);
|
throw new Error(`Invalid date string format: ${str}`);
|
||||||
}
|
}
|
||||||
const wholeString = parts.join(" ");
|
|
||||||
const date = Date.parse(wholeString);
|
// Parse time part
|
||||||
|
const [hours, minutes, seconds] = timePart.split(":");
|
||||||
|
const paddedHours = hours.padStart(2, "0"); // Ensure two-digit hours
|
||||||
|
|
||||||
|
// Combine date and time parts, ensuring two-digit hours
|
||||||
|
const dateTimeString = `${datePart}T${paddedHours}:${minutes}:${seconds.split(".")[0]}Z`;
|
||||||
|
|
||||||
|
const date = Date.parse(dateTimeString);
|
||||||
|
|
||||||
if (Number.isNaN(date)) {
|
if (Number.isNaN(date)) {
|
||||||
throw new Error(
|
throw new Error(`Date string could not be parsed: ${str}`);
|
||||||
`Date string could not be parsed Str: ${str} Parts: ${parts}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,13 +57,15 @@ export function getLogsAndStringsFromRawFileString(
|
||||||
return getLinesOfString(rawFileData).map((line) => {
|
return getLinesOfString(rawFileData).map((line) => {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(line);
|
return JSON.parse(line);
|
||||||
} catch (e) {
|
} catch {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLogsFromRawFileString(rawFileData: string): CliLog[] {
|
export function getLogsFromRawFileString(rawFileData: string): CliLog[] {
|
||||||
|
// TODO: Reimplement this using Tauri
|
||||||
|
return [];
|
||||||
return getLogsAndStringsFromRawFileString(rawFileData).filter(isCliLog);
|
return getLogsAndStringsFromRawFileString(rawFileData).filter(isCliLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1785
src-gui/yarn.lock
1785
src-gui/yarn.lock
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue