mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-11-27 19:20:32 -05: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
|
|
@ -1,5 +1,5 @@
|
|||
import QRCode from "react-qr-code";
|
||||
import { Box } from "@material-ui/core";
|
||||
import QRCode from "react-qr-code";
|
||||
|
||||
export default function BitcoinQrCode({ address }: { address: string }) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ReactNode } from "react";
|
||||
import BitcoinIcon from "renderer/components/icons/BitcoinIcon";
|
||||
import { isTestnet } from "store/config";
|
||||
import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
|
||||
import BitcoinIcon from "renderer/components/icons/BitcoinIcon";
|
||||
import { ReactNode } from "react";
|
||||
import TransactionInfoBox from "./TransactionInfoBox";
|
||||
|
||||
type Props = {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { ReactNode } from "react";
|
||||
import { Box, Typography } from "@material-ui/core";
|
||||
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
|
||||
import InfoBox from "./InfoBox";
|
||||
import ClipboardIconButton from "./ClipbiardIconButton";
|
||||
import { ReactNode } from "react";
|
||||
import BitcoinQrCode from "./BitcoinQrCode";
|
||||
import ClipboardIconButton from "./ClipbiardIconButton";
|
||||
import InfoBox from "./InfoBox";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ReactNode } from "react";
|
||||
import MoneroIcon from "renderer/components/icons/MoneroIcon";
|
||||
import { isTestnet } from "store/config";
|
||||
import { getMoneroTxExplorerUrl } from "utils/conversionUtils";
|
||||
import MoneroIcon from "renderer/components/icons/MoneroIcon";
|
||||
import { ReactNode } from "react";
|
||||
import TransactionInfoBox from "./TransactionInfoBox";
|
||||
|
||||
type Props = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
|
|
@ -6,13 +5,14 @@ import {
|
|||
DialogContent,
|
||||
makeStyles,
|
||||
} from "@material-ui/core";
|
||||
import { useAppDispatch, useAppSelector } from "store/hooks";
|
||||
import { useState } from "react";
|
||||
import { swapReset } from "store/features/swapSlice";
|
||||
import SwapStatePage from "./pages/SwapStatePage";
|
||||
import SwapStateStepper from "./SwapStateStepper";
|
||||
import { useAppDispatch, useAppSelector, useIsSwapRunning } from "store/hooks";
|
||||
import SwapSuspendAlert from "../SwapSuspendAlert";
|
||||
import SwapDialogTitle from "./SwapDialogTitle";
|
||||
import DebugPage from "./pages/DebugPage";
|
||||
import SwapStatePage from "./pages/SwapStatePage";
|
||||
import SwapDialogTitle from "./SwapDialogTitle";
|
||||
import SwapStateStepper from "./SwapStateStepper";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
content: {
|
||||
|
|
@ -32,16 +32,17 @@ export default function SwapDialog({
|
|||
}) {
|
||||
const classes = useStyles();
|
||||
const swap = useAppSelector((state) => state.swap);
|
||||
const isSwapRunning = useIsSwapRunning();
|
||||
const [debug, setDebug] = useState(false);
|
||||
const [openSuspendAlert, setOpenSuspendAlert] = useState(false);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
function onCancel() {
|
||||
if (swap.processRunning) {
|
||||
if (isSwapRunning) {
|
||||
setOpenSuspendAlert(true);
|
||||
} else {
|
||||
onClose();
|
||||
setTimeout(() => dispatch(swapReset()), 0);
|
||||
dispatch(swapReset());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +62,7 @@ export default function SwapDialog({
|
|||
<DebugPage />
|
||||
) : (
|
||||
<>
|
||||
<SwapStatePage swapState={swap.state} />
|
||||
<SwapStatePage state={swap.state} />
|
||||
<SwapStateStepper />
|
||||
</>
|
||||
)}
|
||||
|
|
@ -75,7 +76,7 @@ export default function SwapDialog({
|
|||
color="primary"
|
||||
variant="contained"
|
||||
onClick={onCancel}
|
||||
disabled={!(swap.state !== null && !swap.processRunning)}
|
||||
disabled={isSwapRunning}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
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 FeedbackSubmitBadge from "./pages/FeedbackSubmitBadge";
|
||||
import TorStatusBadge from "./pages/TorStatusBadge";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { Step, StepLabel, Stepper, Typography } from "@material-ui/core";
|
||||
import { SwapSpawnType } from "models/cliModel";
|
||||
import { SwapStateName } from "models/rpcModel";
|
||||
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
||||
import { BobStateName } from "models/tauriModelExt";
|
||||
import {
|
||||
useActiveSwapInfo,
|
||||
useAppSelector,
|
||||
useIsSwapRunning,
|
||||
} from "store/hooks";
|
||||
import { exhaustiveGuard } from "utils/typescriptUtils";
|
||||
|
||||
export enum PathType {
|
||||
|
|
@ -9,8 +13,10 @@ export enum PathType {
|
|||
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(
|
||||
stateName: SwapStateName | null,
|
||||
stateName: BobStateName | null,
|
||||
processExited: boolean,
|
||||
): [PathType, number, boolean] {
|
||||
switch (stateName) {
|
||||
|
|
@ -18,56 +24,56 @@ function getActiveStep(
|
|||
// Step: 0 (Waiting for Bitcoin lock tx to be published)
|
||||
case null:
|
||||
return [PathType.HAPPY_PATH, 0, false];
|
||||
case SwapStateName.Started:
|
||||
case SwapStateName.SwapSetupCompleted:
|
||||
case BobStateName.Started:
|
||||
case BobStateName.SwapSetupCompleted:
|
||||
return [PathType.HAPPY_PATH, 0, processExited];
|
||||
|
||||
// Step: 1 (Waiting for Bitcoin Lock confirmation and XMR Lock Publication)
|
||||
// We have locked the Bitcoin and are waiting for the other party to lock their XMR
|
||||
case SwapStateName.BtcLocked:
|
||||
case BobStateName.BtcLocked:
|
||||
return [PathType.HAPPY_PATH, 1, processExited];
|
||||
|
||||
// Step: 2 (Waiting for XMR Lock confirmation)
|
||||
// We have locked the Bitcoin and the other party has locked their XMR
|
||||
case SwapStateName.XmrLockProofReceived:
|
||||
case BobStateName.XmrLockProofReceived:
|
||||
return [PathType.HAPPY_PATH, 1, processExited];
|
||||
|
||||
// Step: 3 (Sending Encrypted Signature and waiting for Bitcoin Redemption)
|
||||
// The XMR lock transaction has been confirmed
|
||||
// We now need to send the encrypted signature to the other party and wait for them to redeem the Bitcoin
|
||||
case SwapStateName.XmrLocked:
|
||||
case SwapStateName.EncSigSent:
|
||||
case BobStateName.XmrLocked:
|
||||
case BobStateName.EncSigSent:
|
||||
return [PathType.HAPPY_PATH, 2, processExited];
|
||||
|
||||
// Step: 4 (Waiting for XMR Redemption)
|
||||
case SwapStateName.BtcRedeemed:
|
||||
case BobStateName.BtcRedeemed:
|
||||
return [PathType.HAPPY_PATH, 3, processExited];
|
||||
|
||||
// Step: 4 (Completed) (Swap completed, XMR redeemed)
|
||||
case SwapStateName.XmrRedeemed:
|
||||
case BobStateName.XmrRedeemed:
|
||||
return [PathType.HAPPY_PATH, 4, false];
|
||||
|
||||
// Edge Case of Happy Path where the swap is safely aborted. We "fail" at the first step.
|
||||
case SwapStateName.SafelyAborted:
|
||||
case BobStateName.SafelyAborted:
|
||||
return [PathType.HAPPY_PATH, 0, true];
|
||||
|
||||
// // Unhappy Path
|
||||
// Step: 1 (Cancelling swap, checking if cancel transaction has been published already by the other party)
|
||||
case SwapStateName.CancelTimelockExpired:
|
||||
case BobStateName.CancelTimelockExpired:
|
||||
return [PathType.UNHAPPY_PATH, 0, processExited];
|
||||
|
||||
// Step: 2 (Attempt to publish the Bitcoin refund transaction)
|
||||
case SwapStateName.BtcCancelled:
|
||||
case BobStateName.BtcCancelled:
|
||||
return [PathType.UNHAPPY_PATH, 1, processExited];
|
||||
|
||||
// Step: 2 (Completed) (Bitcoin refunded)
|
||||
case SwapStateName.BtcRefunded:
|
||||
case BobStateName.BtcRefunded:
|
||||
return [PathType.UNHAPPY_PATH, 2, false];
|
||||
|
||||
// Step: 2 (We failed to publish the Bitcoin refund transaction)
|
||||
// We failed to publish the Bitcoin refund transaction because the timelock has expired.
|
||||
// We will be punished. Nothing we can do about it now.
|
||||
case SwapStateName.BtcPunished:
|
||||
case BobStateName.BtcPunished:
|
||||
return [PathType.UNHAPPY_PATH, 1, true];
|
||||
default:
|
||||
return exhaustiveGuard(stateName);
|
||||
|
|
@ -149,11 +155,14 @@ function UnhappyPathStepper({
|
|||
}
|
||||
|
||||
export default function SwapStateStepper() {
|
||||
// TODO: There's no equivalent of this with Tauri yet.
|
||||
const currentSwapSpawnType = useAppSelector((s) => s.swap.spawnType);
|
||||
|
||||
const stateName = useActiveSwapInfo()?.state_name ?? null;
|
||||
const processExited = useAppSelector((s) => !s.swap.processRunning);
|
||||
const processExited = !useIsSwapRunning();
|
||||
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 (currentSwapSpawnType === SwapSpawnType.CANCEL_REFUND) {
|
||||
return <UnhappyPathStepper activeStep={0} error={error} />;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Box, DialogContentText } from "@material-ui/core";
|
||||
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
||||
import CliLogsBox from "../../../other/RenderedCliLog";
|
||||
import JsonTreeView from "../../../other/JSONViewTree";
|
||||
import CliLogsBox from "../../../other/RenderedCliLog";
|
||||
|
||||
export default function DebugPage() {
|
||||
const torStdOut = useAppSelector((s) => s.tor.stdOut);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { IconButton } from "@material-ui/core";
|
||||
import FeedbackIcon from "@material-ui/icons/Feedback";
|
||||
import FeedbackDialog from "../../feedback/FeedbackDialog";
|
||||
import { useState } from "react";
|
||||
import FeedbackDialog from "../../feedback/FeedbackDialog";
|
||||
|
||||
export default function FeedbackSubmitBadge() {
|
||||
const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
|
||||
|
|
|
|||
|
|
@ -1,43 +1,28 @@
|
|||
import { Box } from "@material-ui/core";
|
||||
import { useAppSelector } from "store/hooks";
|
||||
import {
|
||||
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 { SwapSlice } from "models/storeModel";
|
||||
import CircularProgressWithSubtitle from "../CircularProgressWithSubtitle";
|
||||
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({
|
||||
swapState,
|
||||
state,
|
||||
}: {
|
||||
swapState: SwapState | null;
|
||||
state: SwapSlice["state"];
|
||||
}) {
|
||||
// TODO: Reimplement this using tauri events
|
||||
/*
|
||||
const isSyncingMoneroWallet = useAppSelector(
|
||||
(state) => state.rpc.state.moneroWallet.isSyncing,
|
||||
);
|
||||
|
|
@ -45,62 +30,57 @@ export default function SwapStatePage({
|
|||
if (isSyncingMoneroWallet) {
|
||||
return <SyncingMoneroWalletPage />;
|
||||
}
|
||||
*/
|
||||
|
||||
if (swapState === null) {
|
||||
if (state === null) {
|
||||
return <InitPage />;
|
||||
}
|
||||
if (isSwapStateInitiated(swapState)) {
|
||||
return <InitiatedPage />;
|
||||
switch (state.curr.type) {
|
||||
case "Initiated":
|
||||
return <InitiatedPage />;
|
||||
case "ReceivedQuote":
|
||||
return <ReceivedQuotePage />;
|
||||
case "WaitingForBtcDeposit":
|
||||
return <WaitingForBitcoinDepositPage {...state.curr.content} />;
|
||||
case "Started":
|
||||
return <StartedPage {...state.curr.content} />;
|
||||
case "BtcLockTxInMempool":
|
||||
return <BitcoinLockTxInMempoolPage {...state.curr.content} />;
|
||||
case "XmrLockTxInMempool":
|
||||
return <XmrLockTxInMempoolPage {...state.curr.content} />;
|
||||
case "XmrLocked":
|
||||
return <XmrLockedPage />;
|
||||
case "BtcRedeemed":
|
||||
return <BitcoinRedeemedPage />;
|
||||
case "XmrRedeemInMempool":
|
||||
return <XmrRedeemInMempoolPage {...state.curr.content} />;
|
||||
case "BtcCancelled":
|
||||
return <BitcoinCancelledPage />;
|
||||
case "BtcRefunded":
|
||||
return <BitcoinRefundedPage {...state.curr.content} />;
|
||||
case "BtcPunished":
|
||||
return <BitcoinPunishedPage />;
|
||||
case "AttemptingCooperativeRedeem":
|
||||
return (
|
||||
<CircularProgressWithSubtitle description="Attempting to redeem the Monero with the help of the other party" />
|
||||
);
|
||||
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 (
|
||||
<Box>
|
||||
No information to display
|
||||
<br />
|
||||
State: {JSON.stringify(state, null, 4)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
if (isSwapStateReceivedQuote(swapState)) {
|
||||
return <ReceivedQuotePage />;
|
||||
}
|
||||
if (isSwapStateWaitingForBtcDeposit(swapState)) {
|
||||
return <WaitingForBitcoinDepositPage state={swapState} />;
|
||||
}
|
||||
if (isSwapStateStarted(swapState)) {
|
||||
return <StartedPage state={swapState} />;
|
||||
}
|
||||
if (isSwapStateBtcLockInMempool(swapState)) {
|
||||
return <BitcoinLockTxInMempoolPage state={swapState} />;
|
||||
}
|
||||
if (isSwapStateXmrLockInMempool(swapState)) {
|
||||
return <XmrLockTxInMempoolPage state={swapState} />;
|
||||
}
|
||||
if (isSwapStateXmrLocked(swapState)) {
|
||||
return <XmrLockedPage />;
|
||||
}
|
||||
if (isSwapStateBtcRedemeed(swapState)) {
|
||||
return <BitcoinRedeemedPage />;
|
||||
}
|
||||
if (isSwapStateXmrRedeemInMempool(swapState)) {
|
||||
return <XmrRedeemInMempoolPage state={swapState} />;
|
||||
}
|
||||
if (isSwapStateBtcCancelled(swapState)) {
|
||||
return <BitcoinCancelledPage />;
|
||||
}
|
||||
if (isSwapStateBtcRefunded(swapState)) {
|
||||
return <BitcoinRefundedPage state={swapState} />;
|
||||
}
|
||||
if (isSwapStateBtcPunished(swapState)) {
|
||||
return <BitcoinPunishedPage />;
|
||||
}
|
||||
if (isSwapStateProcessExited(swapState)) {
|
||||
return <ProcessExitedPage state={swapState} />;
|
||||
}
|
||||
|
||||
console.error(
|
||||
`No swap state page found for swap state State: ${JSON.stringify(
|
||||
swapState,
|
||||
null,
|
||||
4,
|
||||
)}`,
|
||||
);
|
||||
return (
|
||||
<Box>
|
||||
No information to display
|
||||
<br />
|
||||
State: ${JSON.stringify(swapState, null, 4)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import { Box, DialogContentText } from "@material-ui/core";
|
||||
import { SwapStateBtcRefunded } from "models/storeModel";
|
||||
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||
import { useActiveSwapInfo } from "store/hooks";
|
||||
import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
|
||||
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
|
||||
import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
|
||||
|
||||
export default function BitcoinRefundedPage({
|
||||
state,
|
||||
}: {
|
||||
state: SwapStateBtcRefunded | null;
|
||||
}) {
|
||||
btc_refund_txid,
|
||||
}: TauriSwapProgressEventContent<"BtcRefunded">) {
|
||||
// TODO: Reimplement this using Tauri
|
||||
const swap = useActiveSwapInfo();
|
||||
const additionalContent = swap
|
||||
? `Refund address: ${swap.btc_refund_address}`
|
||||
|
|
@ -28,14 +27,15 @@ export default function BitcoinRefundedPage({
|
|||
gap: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{state && (
|
||||
<BitcoinTransactionInfoBox
|
||||
title="Bitcoin Refund Transaction"
|
||||
txId={state.bobBtcRefundTxId}
|
||||
loading={false}
|
||||
additionalContent={additionalContent}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
// TODO: We should display the confirmation count here
|
||||
}
|
||||
<BitcoinTransactionInfoBox
|
||||
title="Bitcoin Refund Transaction"
|
||||
txId={btc_refund_txid}
|
||||
loading={false}
|
||||
additionalContent={additionalContent}
|
||||
/>
|
||||
<FeedbackInfoBox />
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,18 @@
|
|||
import { Box, DialogContentText } from "@material-ui/core";
|
||||
import { SwapStateXmrRedeemInMempool } from "models/storeModel";
|
||||
import { useActiveSwapInfo } from "store/hooks";
|
||||
import { getSwapXmrAmount } from "models/rpcModel";
|
||||
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
|
||||
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
|
||||
|
||||
type XmrRedeemInMempoolPageProps = {
|
||||
state: SwapStateXmrRedeemInMempool | null;
|
||||
};
|
||||
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
|
||||
|
||||
export default function XmrRedeemInMempoolPage({
|
||||
state,
|
||||
}: XmrRedeemInMempoolPageProps) {
|
||||
const swap = useActiveSwapInfo();
|
||||
const additionalContent = swap
|
||||
? `This transaction transfers ${getSwapXmrAmount(swap).toFixed(6)} XMR to ${
|
||||
state?.bobXmrRedeemAddress
|
||||
}`
|
||||
: null;
|
||||
xmr_redeem_address,
|
||||
xmr_redeem_txid,
|
||||
}: TauriSwapProgressEventContent<"XmrRedeemInMempool">) {
|
||||
// TODO: Reimplement this using Tauri
|
||||
//const additionalContent = swap
|
||||
// ? `This transaction transfers ${getSwapXmrAmount(swap).toFixed(6)} XMR to ${
|
||||
// state?.bobXmrRedeemAddress
|
||||
// }`
|
||||
// : null;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
|
|
@ -32,16 +27,12 @@ export default function XmrRedeemInMempoolPage({
|
|||
gap: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{state && (
|
||||
<>
|
||||
<MoneroTransactionInfoBox
|
||||
title="Monero Redeem Transaction"
|
||||
txId={state.bobXmrRedeemTxId}
|
||||
additionalContent={additionalContent}
|
||||
loading={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<MoneroTransactionInfoBox
|
||||
title="Monero Redeem Transaction"
|
||||
txId={xmr_redeem_txid}
|
||||
additionalContent={`The funds have been sent to the address ${xmr_redeem_address}`}
|
||||
loading={false}
|
||||
/>
|
||||
<FeedbackInfoBox />
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
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 { SwapStateProcessExited } from "models/storeModel";
|
||||
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
||||
import CliLogsBox from "../../../../other/RenderedCliLog";
|
||||
|
||||
export default function ProcessExitedAndNotDonePage({
|
||||
state,
|
||||
|
|
@ -18,7 +18,7 @@ export default function ProcessExitedAndNotDonePage({
|
|||
const hasRpcError = state.rpcError != null;
|
||||
const hasSwap = swap != null;
|
||||
|
||||
let messages = [];
|
||||
const messages = [];
|
||||
|
||||
messages.push(
|
||||
isCancelRefund
|
||||
|
|
|
|||
|
|
@ -1,47 +1,41 @@
|
|||
import { useActiveSwapInfo } from "store/hooks";
|
||||
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 { TauriSwapProgressEvent } from "models/tauriModel";
|
||||
import SwapStatePage from "../SwapStatePage";
|
||||
import BitcoinRefundedPage from "../done/BitcoinRefundedPage";
|
||||
import ProcessExitedAndNotDonePage from "./ProcessExitedAndNotDonePage";
|
||||
|
||||
type ProcessExitedPageProps = {
|
||||
state: SwapStateProcessExited;
|
||||
};
|
||||
|
||||
export default function ProcessExitedPage({ state }: ProcessExitedPageProps) {
|
||||
const swap = useActiveSwapInfo();
|
||||
|
||||
// If we have a swap state, for a "done" state we should use it to display additional information that can't be extracted from the database
|
||||
export default function ProcessExitedPage({
|
||||
prevState,
|
||||
swapId,
|
||||
}: {
|
||||
prevState: TauriSwapProgressEvent | null;
|
||||
swapId: string;
|
||||
}) {
|
||||
// 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 (
|
||||
isSwapStateXmrRedeemInMempool(state.prevState) ||
|
||||
isSwapStateBtcRefunded(state.prevState) ||
|
||||
isSwapStateBtcPunished(state.prevState)
|
||||
prevState != null &&
|
||||
(prevState.type === "XmrRedeemInMempool" ||
|
||||
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
|
||||
if (swap) {
|
||||
if (swap.state_name === SwapStateName.XmrRedeemed) {
|
||||
return <XmrRedeemInMempoolPage state={null} />;
|
||||
}
|
||||
if (swap.state_name === SwapStateName.BtcRefunded) {
|
||||
return <BitcoinRefundedPage state={null} />;
|
||||
}
|
||||
if (swap.state_name === SwapStateName.BtcPunished) {
|
||||
return <BitcoinPunishedPage />;
|
||||
}
|
||||
}
|
||||
// TODO: Display something useful here
|
||||
return (
|
||||
<>
|
||||
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
|
||||
Not implemented yet
|
||||
</>
|
||||
);
|
||||
|
||||
// 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 { SwapStateBtcLockInMempool } from "models/storeModel";
|
||||
import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
|
||||
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||
import SwapMightBeCancelledAlert from "../../../../alert/SwapMightBeCancelledAlert";
|
||||
|
||||
type BitcoinLockTxInMempoolPageProps = {
|
||||
state: SwapStateBtcLockInMempool;
|
||||
};
|
||||
import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
|
||||
|
||||
export default function BitcoinLockTxInMempoolPage({
|
||||
state,
|
||||
}: BitcoinLockTxInMempoolPageProps) {
|
||||
btc_lock_confirmations,
|
||||
btc_lock_txid,
|
||||
}: TauriSwapProgressEventContent<"BtcLockTxInMempool">) {
|
||||
return (
|
||||
<Box>
|
||||
<SwapMightBeCancelledAlert
|
||||
bobBtcLockTxConfirmations={state.bobBtcLockTxConfirmations}
|
||||
bobBtcLockTxConfirmations={btc_lock_confirmations}
|
||||
/>
|
||||
<DialogContentText>
|
||||
The Bitcoin lock transaction has been published. The swap will proceed
|
||||
|
|
@ -22,14 +19,14 @@ export default function BitcoinLockTxInMempoolPage({
|
|||
</DialogContentText>
|
||||
<BitcoinTransactionInfoBox
|
||||
title="Bitcoin Lock Transaction"
|
||||
txId={state.bobBtcLockTxId}
|
||||
txId={btc_lock_txid}
|
||||
loading
|
||||
additionalContent={
|
||||
<>
|
||||
Most swap providers require one confirmation before locking their
|
||||
Monero
|
||||
<br />
|
||||
Confirmations: {state.bobBtcLockTxConfirmations}
|
||||
Confirmations: {btc_lock_confirmations}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import { SwapStateStarted } from "models/storeModel";
|
||||
import { BitcoinAmount } from "renderer/components/other/Units";
|
||||
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||
import { SatsAmount } from "renderer/components/other/Units";
|
||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||
|
||||
export default function StartedPage({ state }: { state: SwapStateStarted }) {
|
||||
const description = state.txLockDetails ? (
|
||||
<>
|
||||
Locking <BitcoinAmount amount={state.txLockDetails.amount} /> with a
|
||||
network fee of <BitcoinAmount amount={state.txLockDetails.fees} />
|
||||
</>
|
||||
) : (
|
||||
"Locking Bitcoin"
|
||||
export default function StartedPage({
|
||||
btc_lock_amount,
|
||||
btc_tx_lock_fee,
|
||||
}: TauriSwapProgressEventContent<"Started">) {
|
||||
return (
|
||||
<CircularProgressWithSubtitle
|
||||
description={
|
||||
<>
|
||||
Locking <SatsAmount amount={btc_lock_amount} /> with a network fee of{" "}
|
||||
<SatsAmount amount={btc_tx_lock_fee} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
return <CircularProgressWithSubtitle description={description} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
import { Box, DialogContentText } from "@material-ui/core";
|
||||
import { SwapStateXmrLockInMempool } from "models/storeModel";
|
||||
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
|
||||
|
||||
type XmrLockTxInMempoolPageProps = {
|
||||
state: SwapStateXmrLockInMempool;
|
||||
};
|
||||
|
||||
export default function XmrLockTxInMempoolPage({
|
||||
state,
|
||||
}: XmrLockTxInMempoolPageProps) {
|
||||
const additionalContent = `Confirmations: ${state.aliceXmrLockTxConfirmations}/10`;
|
||||
xmr_lock_tx_confirmations,
|
||||
xmr_lock_txid,
|
||||
}: TauriSwapProgressEventContent<"XmrLockTxInMempool">) {
|
||||
const additionalContent = `Confirmations: ${xmr_lock_tx_confirmations}/10`;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
|
|
@ -20,7 +17,7 @@ export default function XmrLockTxInMempoolPage({
|
|||
|
||||
<MoneroTransactionInfoBox
|
||||
title="Monero Lock Transaction"
|
||||
txId={state.aliceXmrLockTxId}
|
||||
txId={xmr_lock_txid}
|
||||
additionalContent={additionalContent}
|
||||
loading
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useState } from "react";
|
||||
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 { satsToBtc } from "utils/conversionUtils";
|
||||
import { btcToSats, satsToBtc } from "utils/conversionUtils";
|
||||
import { MoneroAmount } from "../../../../other/Units";
|
||||
|
||||
const MONERO_FEE = 0.000016;
|
||||
|
|
@ -29,42 +29,42 @@ function calcBtcAmountWithoutFees(amount: number, fees: number) {
|
|||
}
|
||||
|
||||
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 [amount, setAmount] = useState(state.minDeposit);
|
||||
const [amount, setAmount] = useState(min_deposit_until_swap_will_start);
|
||||
const bitcoinBalance = useAppSelector((s) => s.rpc.state.balance) || 0;
|
||||
|
||||
function getTotalAmountAfterDeposit() {
|
||||
return amount + satsToBtc(bitcoinBalance);
|
||||
return amount + bitcoinBalance;
|
||||
}
|
||||
|
||||
function hasError() {
|
||||
return (
|
||||
amount < state.minDeposit ||
|
||||
getTotalAmountAfterDeposit() > state.maximumAmount
|
||||
amount < min_deposit_until_swap_will_start ||
|
||||
getTotalAmountAfterDeposit() > max_deposit_until_maximum_amount_is_reached
|
||||
);
|
||||
}
|
||||
|
||||
function calcXMRAmount(): number | null {
|
||||
if (Number.isNaN(amount)) return null;
|
||||
if (hasError()) return null;
|
||||
if (state.price == null) return null;
|
||||
|
||||
console.log(
|
||||
`Calculating calcBtcAmountWithoutFees(${getTotalAmountAfterDeposit()}, ${
|
||||
state.minBitcoinLockTxFee
|
||||
}) / ${state.price} - ${MONERO_FEE}`,
|
||||
);
|
||||
if (quote.price == null) return null;
|
||||
|
||||
return (
|
||||
calcBtcAmountWithoutFees(
|
||||
getTotalAmountAfterDeposit(),
|
||||
state.minBitcoinLockTxFee,
|
||||
min_bitcoin_lock_tx_fee,
|
||||
) /
|
||||
state.price -
|
||||
quote.price -
|
||||
MONERO_FEE
|
||||
);
|
||||
}
|
||||
|
|
@ -75,9 +75,9 @@ export default function DepositAmountHelper({
|
|||
Depositing {bitcoinBalance > 0 && <>another</>}
|
||||
</Typography>
|
||||
<TextField
|
||||
error={hasError()}
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(parseFloat(e.target.value))}
|
||||
error={!!hasError()}
|
||||
value={satsToBtc(amount)}
|
||||
onChange={(e) => setAmount(btcToSats(parseFloat(e.target.value)))}
|
||||
size="small"
|
||||
type="number"
|
||||
className={classes.textField}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||
import { MoneroWalletRpcUpdateState } from "../../../../../../models/storeModel";
|
||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||
|
||||
export default function DownloadingMoneroWalletRpcPage({
|
||||
updateState,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { Box, DialogContentText, makeStyles } from "@material-ui/core";
|
||||
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
||||
import { useState } from "react";
|
||||
import BitcoinAddressTextField from "renderer/components/inputs/BitcoinAddressTextField";
|
||||
import MoneroAddressTextField from "renderer/components/inputs/MoneroAddressTextField";
|
||||
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||
import { buyXmr } from "renderer/rpc";
|
||||
import { useAppSelector } from "store/hooks";
|
||||
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
|
||||
import { isTestnet } from "store/config";
|
||||
import RemainingFundsWillBeUsedAlert from "../../../../alert/RemainingFundsWillBeUsedAlert";
|
||||
import IpcInvokeButton from "../../../../IpcInvokeButton";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
initButton: {
|
||||
|
|
@ -29,6 +29,10 @@ export default function InitPage() {
|
|||
(state) => state.providers.selectedProvider,
|
||||
);
|
||||
|
||||
async function init() {
|
||||
await buyXmr(selectedProvider, refundAddress, redeemAddress);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<RemainingFundsWillBeUsedAlert />
|
||||
|
|
@ -58,7 +62,7 @@ export default function InitPage() {
|
|||
/>
|
||||
</Box>
|
||||
|
||||
<IpcInvokeButton
|
||||
<PromiseInvokeButton
|
||||
disabled={
|
||||
!refundAddressValid || !redeemAddressValid || !selectedProvider
|
||||
}
|
||||
|
|
@ -67,12 +71,10 @@ export default function InitPage() {
|
|||
size="large"
|
||||
className={classes.initButton}
|
||||
endIcon={<PlayArrowIcon />}
|
||||
ipcChannel="spawn-buy-xmr"
|
||||
ipcArgs={[selectedProvider, redeemAddress, refundAddress]}
|
||||
displayErrorSnackbar={false}
|
||||
onClick={init}
|
||||
>
|
||||
Start swap
|
||||
</IpcInvokeButton>
|
||||
</PromiseInvokeButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useAppSelector } from "store/hooks";
|
||||
import { SwapSpawnType } from "models/cliModel";
|
||||
import { useAppSelector } from "store/hooks";
|
||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||
|
||||
export default function InitiatedPage() {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import { Box, makeStyles, Typography } from "@material-ui/core";
|
||||
import { SwapStateWaitingForBtcDeposit } from "models/storeModel";
|
||||
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||
import { useAppSelector } from "store/hooks";
|
||||
import DepositAddressInfoBox from "../../DepositAddressInfoBox";
|
||||
import BitcoinIcon from "../../../../icons/BitcoinIcon";
|
||||
import { MoneroSatsExchangeRate, SatsAmount } from "../../../../other/Units";
|
||||
import DepositAddressInfoBox from "../../DepositAddressInfoBox";
|
||||
import DepositAmountHelper from "./DepositAmountHelper";
|
||||
import {
|
||||
BitcoinAmount,
|
||||
MoneroBitcoinExchangeRate,
|
||||
SatsAmount,
|
||||
} from "../../../../other/Units";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
amountHelper: {
|
||||
|
|
@ -23,13 +19,13 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
type WaitingForBtcDepositPageProps = {
|
||||
state: SwapStateWaitingForBtcDeposit;
|
||||
};
|
||||
|
||||
export default function WaitingForBtcDepositPage({
|
||||
state,
|
||||
}: WaitingForBtcDepositPageProps) {
|
||||
deposit_address,
|
||||
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 bitcoinBalance = useAppSelector((s) => s.rpc.state.balance) || 0;
|
||||
|
||||
|
|
@ -38,7 +34,7 @@ export default function WaitingForBtcDepositPage({
|
|||
<Box>
|
||||
<DepositAddressInfoBox
|
||||
title="Bitcoin Deposit Address"
|
||||
address={state.depositAddress}
|
||||
address={deposit_address}
|
||||
additionalContent={
|
||||
<Box className={classes.additionalContent}>
|
||||
<Typography variant="subtitle2">
|
||||
|
|
@ -51,9 +47,11 @@ export default function WaitingForBtcDepositPage({
|
|||
) : null}
|
||||
<li>
|
||||
Send any amount between{" "}
|
||||
<BitcoinAmount amount={state.minDeposit} /> and{" "}
|
||||
<BitcoinAmount amount={state.maxDeposit} /> to the address
|
||||
above
|
||||
<SatsAmount amount={min_deposit_until_swap_will_start} /> and{" "}
|
||||
<SatsAmount
|
||||
amount={max_deposit_until_maximum_amount_is_reached}
|
||||
/>{" "}
|
||||
to the address above
|
||||
{bitcoinBalance > 0 && (
|
||||
<> (on top of the already deposited funds)</>
|
||||
)}
|
||||
|
|
@ -61,11 +59,11 @@ export default function WaitingForBtcDepositPage({
|
|||
<li>
|
||||
All Bitcoin sent to this this address will converted into
|
||||
Monero at an exchance rate of{" "}
|
||||
<MoneroBitcoinExchangeRate rate={state.price} />
|
||||
<MoneroSatsExchangeRate rate={quote.price} />
|
||||
</li>
|
||||
<li>
|
||||
The network fee of{" "}
|
||||
<BitcoinAmount amount={state.minBitcoinLockTxFee} /> will
|
||||
<SatsAmount amount={min_bitcoin_lock_tx_fee} /> will
|
||||
automatically be deducted from the deposited coins
|
||||
</li>
|
||||
<li>
|
||||
|
|
@ -74,7 +72,16 @@ export default function WaitingForBtcDepositPage({
|
|||
</li>
|
||||
</ul>
|
||||
</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>
|
||||
}
|
||||
icon={<BitcoinIcon />}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue