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:
binarybaron 2024-08-26 15:32:28 +02:00
parent d54f5c6c77
commit cf641bc8bb
No known key found for this signature in database
GPG key ID: 99B75D3E1476A26E
77 changed files with 2484 additions and 2167 deletions

View file

@ -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 (

View file

@ -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 = {

View file

@ -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;

View file

@ -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 = {

View file

@ -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>

View file

@ -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: {

View file

@ -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} />;

View file

@ -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);

View file

@ -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);

View file

@ -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>
);
}

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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} />;
}

View file

@ -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}
</>
}
/>

View file

@ -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} />;
}

View file

@ -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
/>

View file

@ -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}

View file

@ -1,5 +1,5 @@
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
import { MoneroWalletRpcUpdateState } from "../../../../../../models/storeModel";
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
export default function DownloadingMoneroWalletRpcPage({
updateState,

View file

@ -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>
);
}

View file

@ -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() {

View file

@ -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 />}