mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-12-17 09:34:16 -05:00
refactor (#656)
This commit is contained in:
parent
6db365bf23
commit
c3f3623b6e
21 changed files with 375 additions and 221 deletions
|
|
@ -3,11 +3,11 @@ import { TauriEvent } from "models/tauriModel";
|
||||||
import {
|
import {
|
||||||
contextStatusEventReceived,
|
contextStatusEventReceived,
|
||||||
contextInitializationFailed,
|
contextInitializationFailed,
|
||||||
rpcSetBalance,
|
|
||||||
timelockChangeEventReceived,
|
timelockChangeEventReceived,
|
||||||
approvalEventReceived,
|
approvalEventReceived,
|
||||||
backgroundProgressEventReceived,
|
backgroundProgressEventReceived,
|
||||||
} from "store/features/rpcSlice";
|
} from "store/features/rpcSlice";
|
||||||
|
import { setBitcoinBalance } from "store/features/bitcoinWalletSlice";
|
||||||
import { receivedCliLog } from "store/features/logsSlice";
|
import { receivedCliLog } from "store/features/logsSlice";
|
||||||
import { poolStatusReceived } from "store/features/poolSlice";
|
import { poolStatusReceived } from "store/features/poolSlice";
|
||||||
import { swapProgressEventReceived } from "store/features/swapSlice";
|
import { swapProgressEventReceived } from "store/features/swapSlice";
|
||||||
|
|
@ -118,7 +118,7 @@ listen<TauriEvent>(TAURI_UNIFIED_EVENT_CHANNEL_NAME, (event) => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "BalanceChange":
|
case "BalanceChange":
|
||||||
store.dispatch(rpcSetBalance(eventData.balance));
|
store.dispatch(setBitcoinBalance(eventData.balance));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "SwapDatabaseStateUpdate":
|
case "SwapDatabaseStateUpdate":
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { Button } from "@mui/material";
|
|
||||||
import Alert from "@mui/material/Alert";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { useAppSelector } from "store/hooks";
|
|
||||||
|
|
||||||
export default function FundsLeftInWalletAlert() {
|
|
||||||
const fundsLeft = useAppSelector((state) => state.rpc.state.balance);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
if (fundsLeft != null && fundsLeft > 0) {
|
|
||||||
return (
|
|
||||||
<Alert
|
|
||||||
variant="filled"
|
|
||||||
severity="info"
|
|
||||||
action={
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
|
||||||
onClick={() => navigate("/bitcoin-wallet")}
|
|
||||||
>
|
|
||||||
View
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
There are some Bitcoin left in your wallet
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default function AddressInputPage({
|
||||||
<>
|
<>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
To withdraw the Bitcoin inside the internal wallet, please enter an
|
To withdraw the Bitcoin inside the internal wallet, please enter an
|
||||||
address. All funds will be sent to that address.
|
address. All funds (the entire balance) will be sent to that address.
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
|
|
||||||
<BitcoinAddressTextField
|
<BitcoinAddressTextField
|
||||||
|
|
@ -22,6 +22,7 @@ export default function AddressInputPage({
|
||||||
onAddressChange={setWithdrawAddress}
|
onAddressChange={setWithdrawAddress}
|
||||||
onAddressValidityChange={setWithdrawAddressValid}
|
onAddressValidityChange={setWithdrawAddressValid}
|
||||||
helperText="All Bitcoin of the internal wallet will be transferred to this address"
|
helperText="All Bitcoin of the internal wallet will be transferred to this address"
|
||||||
|
allowEmpty={false}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { Box, Tooltip } from "@mui/material";
|
import { Box, Tooltip } from "@mui/material";
|
||||||
import { BackgroundProgressAlerts } from "../alert/DaemonStatusAlert";
|
import { BackgroundProgressAlerts } from "../alert/DaemonStatusAlert";
|
||||||
import FundsLeftInWalletAlert from "../alert/FundsLeftInWalletAlert";
|
|
||||||
import UnfinishedSwapsAlert from "../alert/UnfinishedSwapsAlert";
|
import UnfinishedSwapsAlert from "../alert/UnfinishedSwapsAlert";
|
||||||
import BackgroundRefundAlert from "../alert/BackgroundRefundAlert";
|
import BackgroundRefundAlert from "../alert/BackgroundRefundAlert";
|
||||||
import ContactInfoBox from "../other/ContactInfoBox";
|
import ContactInfoBox from "../other/ContactInfoBox";
|
||||||
|
|
@ -15,7 +14,6 @@ export default function NavigationFooter() {
|
||||||
gap: 1,
|
gap: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FundsLeftInWalletAlert />
|
|
||||||
<UnfinishedSwapsAlert />
|
<UnfinishedSwapsAlert />
|
||||||
<BackgroundRefundAlert />
|
<BackgroundRefundAlert />
|
||||||
<BackgroundProgressAlerts />
|
<BackgroundProgressAlerts />
|
||||||
|
|
|
||||||
|
|
@ -92,40 +92,43 @@ export default function ActionableMonospaceTextBox({
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
filter: spoilerText && !isRevealed ? "blur(8px)" : "none",
|
filter: spoilerText && !isRevealed ? "blur(8px)" : "none",
|
||||||
transition: "filter 0.3s ease",
|
transition: "filter 0.3s ease",
|
||||||
}}
|
}}
|
||||||
|
onClick={handleCopy}
|
||||||
>
|
>
|
||||||
<Box sx={{ flexGrow: 1 }} onClick={handleCopy}>
|
<MonospaceTextBox
|
||||||
<MonospaceTextBox light={light}>
|
light={light}
|
||||||
{content}
|
actions={
|
||||||
{displayCopyIcon && (
|
<>
|
||||||
<IconButton
|
{displayCopyIcon && (
|
||||||
onClick={handleCopy}
|
<Tooltip title="Copy to clipboard" arrow>
|
||||||
size="small"
|
<IconButton onClick={handleCopy} size="small">
|
||||||
sx={{ marginLeft: 1 }}
|
<FileCopyOutlined />
|
||||||
>
|
</IconButton>
|
||||||
<FileCopyOutlined />
|
</Tooltip>
|
||||||
</IconButton>
|
)}
|
||||||
)}
|
{enableQrCode && (
|
||||||
{enableQrCode && (
|
<Tooltip title="Show QR Code" arrow>
|
||||||
<Tooltip title="Show QR Code" arrow>
|
<IconButton
|
||||||
<IconButton
|
onClick={(e) => {
|
||||||
onClick={() => setQrCodeOpen(true)}
|
e.stopPropagation();
|
||||||
onMouseEnter={() => setIsQrCodeButtonHovered(true)}
|
setQrCodeOpen(true);
|
||||||
onMouseLeave={() => setIsQrCodeButtonHovered(false)}
|
}}
|
||||||
size="small"
|
onMouseEnter={() => setIsQrCodeButtonHovered(true)}
|
||||||
sx={{ marginLeft: 1 }}
|
onMouseLeave={() => setIsQrCodeButtonHovered(false)}
|
||||||
>
|
size="small"
|
||||||
<QrCodeIcon />
|
>
|
||||||
</IconButton>
|
<QrCodeIcon />
|
||||||
</Tooltip>
|
</IconButton>
|
||||||
)}
|
</Tooltip>
|
||||||
</MonospaceTextBox>
|
)}
|
||||||
</Box>
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</MonospaceTextBox>
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,25 @@ import { Box, Typography } from "@mui/material";
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
light?: boolean;
|
light?: boolean;
|
||||||
|
actions?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MonospaceTextBox({ children, light = false }: Props) {
|
export default function MonospaceTextBox({
|
||||||
|
children,
|
||||||
|
light = false,
|
||||||
|
actions,
|
||||||
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
backgroundColor: light ? "transparent" : theme.palette.grey[900],
|
backgroundColor: light ? "transparent" : theme.palette.grey[900],
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
border: light ? `1px solid ${theme.palette.grey[800]}` : "none",
|
border: light ? `1px solid ${theme.palette.grey[800]}` : "none",
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
|
gap: 1,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
|
|
@ -25,12 +32,14 @@ export default function MonospaceTextBox({ children, light = false }: Props) {
|
||||||
whiteSpace: "pre-wrap",
|
whiteSpace: "pre-wrap",
|
||||||
fontFamily: "monospace",
|
fontFamily: "monospace",
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
display: "flex",
|
flex: 1,
|
||||||
alignItems: "center",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{actions && (
|
||||||
|
<Box sx={{ display: "flex", gap: 0.5, flexShrink: 0 }}>{actions}</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Typography,
|
|
||||||
Dialog,
|
|
||||||
DialogTitle,
|
|
||||||
DialogContent,
|
|
||||||
DialogActions,
|
|
||||||
Button,
|
|
||||||
Link,
|
|
||||||
DialogContentText,
|
|
||||||
} from "@mui/material";
|
|
||||||
import InfoBox from "renderer/components/pages/swap/swap/components/InfoBox";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { getWalletDescriptor } from "renderer/rpc";
|
|
||||||
import { ExportBitcoinWalletResponse } from "models/tauriModel";
|
|
||||||
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
|
||||||
import ActionableMonospaceTextBox from "renderer/components/other/ActionableMonospaceTextBox";
|
|
||||||
import { isContextWithBitcoinWallet } from "models/tauriModelExt";
|
|
||||||
|
|
||||||
export default function ExportDataBox() {
|
|
||||||
const [walletDescriptor, setWalletDescriptor] =
|
|
||||||
useState<ExportBitcoinWalletResponse | null>(null);
|
|
||||||
|
|
||||||
const handleCloseDialog = () => {
|
|
||||||
setWalletDescriptor(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InfoBox
|
|
||||||
title="Export Bitcoin Wallet"
|
|
||||||
icon={null}
|
|
||||||
loading={false}
|
|
||||||
mainContent={
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "flex-start",
|
|
||||||
gap: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="subtitle2">
|
|
||||||
You can export the wallet descriptor of the interal Bitcoin wallet
|
|
||||||
for backup or recovery purposes. Please make sure to store it
|
|
||||||
securely.
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
additionalContent={
|
|
||||||
<>
|
|
||||||
<PromiseInvokeButton
|
|
||||||
variant="outlined"
|
|
||||||
onInvoke={getWalletDescriptor}
|
|
||||||
onSuccess={setWalletDescriptor}
|
|
||||||
displayErrorSnackbar={true}
|
|
||||||
contextRequirement={isContextWithBitcoinWallet}
|
|
||||||
>
|
|
||||||
Reveal Bitcoin Wallet Private Key
|
|
||||||
</PromiseInvokeButton>
|
|
||||||
{walletDescriptor !== null && (
|
|
||||||
<WalletDescriptorModal
|
|
||||||
open={walletDescriptor !== null}
|
|
||||||
onClose={handleCloseDialog}
|
|
||||||
walletDescriptor={walletDescriptor}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function WalletDescriptorModal({
|
|
||||||
open,
|
|
||||||
onClose,
|
|
||||||
walletDescriptor,
|
|
||||||
}: {
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
walletDescriptor: ExportBitcoinWalletResponse;
|
|
||||||
}) {
|
|
||||||
const parsedDescriptor = JSON.parse(
|
|
||||||
walletDescriptor.wallet_descriptor["descriptor"],
|
|
||||||
);
|
|
||||||
const stringifiedDescriptor = JSON.stringify(parsedDescriptor, null, 4);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
|
||||||
<DialogTitle>Bitcoin Wallet Descriptor</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogContentText>
|
|
||||||
<ul style={{ marginTop: 0 }}>
|
|
||||||
<li>
|
|
||||||
The text below contains the wallet descriptor of the internal
|
|
||||||
Bitcoin wallet. It contains your private key and can be used to
|
|
||||||
derive your wallet. It should thus be stored securely.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
It can be imported into other Bitcoin wallets or services that
|
|
||||||
support the descriptor format.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
For more information on what to do with the descriptor, see our{" "}
|
|
||||||
<Link
|
|
||||||
href="https://github.com/eigenwallet/core/blob/master/dev-docs/asb/README.md#exporting-the-bitcoin-wallet-descriptor"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
documentation
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</DialogContentText>
|
|
||||||
<ActionableMonospaceTextBox
|
|
||||||
content={stringifiedDescriptor}
|
|
||||||
displayCopyIcon={true}
|
|
||||||
enableQrCode={false}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={onClose} color="primary" variant="contained">
|
|
||||||
Done
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -32,7 +32,6 @@ export default function SettingsPage() {
|
||||||
<SettingsBox />
|
<SettingsBox />
|
||||||
<DaemonControlBox />
|
<DaemonControlBox />
|
||||||
<MoneroPoolHealthBox />
|
<MoneroPoolHealthBox />
|
||||||
<ExportDataBox />
|
|
||||||
<DiscoveryBox />
|
<DiscoveryBox />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export default function ConfirmationsBadge({
|
||||||
return (
|
return (
|
||||||
<Chip
|
<Chip
|
||||||
icon={<AutoAwesomeIcon />}
|
icon={<AutoAwesomeIcon />}
|
||||||
label="Published"
|
label="Unconfirmed"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,32 @@
|
||||||
import { Box, Typography } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { Alert } from "@mui/material";
|
import { useAppSelector } from "store/hooks";
|
||||||
import WithdrawWidget from "./WithdrawWidget";
|
import WalletOverview from "./components/WalletOverview";
|
||||||
|
import WalletActionButtons from "./components/WalletActionButtons";
|
||||||
|
import ActionableMonospaceTextBox from "renderer/components/other/ActionableMonospaceTextBox";
|
||||||
|
|
||||||
export default function WalletPage() {
|
export default function WalletPage() {
|
||||||
|
const walletBalance = useAppSelector((state) => state.bitcoinWallet.balance);
|
||||||
|
const bitcoinAddress = useAppSelector((state) => state.bitcoinWallet.address);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
maxWidth: 800,
|
||||||
|
mx: "auto",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: "1rem",
|
gap: 2,
|
||||||
|
pb: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Alert severity="info">
|
<WalletOverview balance={walletBalance} />
|
||||||
You do not have to deposit money before starting a swap. Instead, you
|
{bitcoinAddress && (
|
||||||
will be greeted with a deposit address after you initiate one.
|
<ActionableMonospaceTextBox
|
||||||
</Alert>
|
content={bitcoinAddress}
|
||||||
<WithdrawWidget />
|
displayCopyIcon={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<WalletActionButtons />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import WithdrawDialog from "../../modal/wallet/WithdrawDialog";
|
||||||
import WalletRefreshButton from "./WalletRefreshButton";
|
import WalletRefreshButton from "./WalletRefreshButton";
|
||||||
|
|
||||||
export default function WithdrawWidget() {
|
export default function WithdrawWidget() {
|
||||||
const walletBalance = useAppSelector((state) => state.rpc.state.balance);
|
const walletBalance = useAppSelector((state) => state.bitcoinWallet.balance);
|
||||||
const [showDialog, setShowDialog] = useState(false);
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
|
|
||||||
function onShowDialog() {
|
function onShowDialog() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Box, Chip } from "@mui/material";
|
||||||
|
import { Send as SendIcon } from "@mui/icons-material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useAppSelector } from "store/hooks";
|
||||||
|
import WithdrawDialog from "../../../modal/wallet/WithdrawDialog";
|
||||||
|
import WalletDescriptorButton from "./WalletDescriptorButton";
|
||||||
|
|
||||||
|
export default function WalletActionButtons() {
|
||||||
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
|
const balance = useAppSelector((state) => state.bitcoinWallet.balance);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WithdrawDialog open={showDialog} onClose={() => setShowDialog(false)} />
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: 1,
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Chip
|
||||||
|
icon={<SendIcon />}
|
||||||
|
label="Sweep"
|
||||||
|
variant="button"
|
||||||
|
clickable
|
||||||
|
onClick={() => setShowDialog(true)}
|
||||||
|
disabled={balance === null || balance <= 0}
|
||||||
|
/>
|
||||||
|
<WalletDescriptorButton />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
DialogContentText,
|
||||||
|
Link,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { Key as KeyIcon } from "@mui/icons-material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import ActionableMonospaceTextBox from "renderer/components/other/ActionableMonospaceTextBox";
|
||||||
|
import { getWalletDescriptor } from "renderer/rpc";
|
||||||
|
import { ExportBitcoinWalletResponse } from "models/tauriModel";
|
||||||
|
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||||
|
import { isContextWithBitcoinWallet } from "models/tauriModelExt";
|
||||||
|
|
||||||
|
const WALLET_DESCRIPTOR_DOCS_URL =
|
||||||
|
"https://github.com/eigenwallet/core/blob/master/dev-docs/asb/README.md#exporting-the-bitcoin-wallet-descriptor";
|
||||||
|
|
||||||
|
export default function WalletDescriptorButton() {
|
||||||
|
const [walletDescriptor, setWalletDescriptor] =
|
||||||
|
useState<ExportBitcoinWalletResponse | null>(null);
|
||||||
|
|
||||||
|
const handleCloseDialog = () => {
|
||||||
|
setWalletDescriptor(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PromiseInvokeButton
|
||||||
|
isChipButton={true}
|
||||||
|
startIcon={<KeyIcon />}
|
||||||
|
onInvoke={getWalletDescriptor}
|
||||||
|
onSuccess={setWalletDescriptor}
|
||||||
|
displayErrorSnackbar={true}
|
||||||
|
contextRequirement={isContextWithBitcoinWallet}
|
||||||
|
>
|
||||||
|
Reveal Private Key
|
||||||
|
</PromiseInvokeButton>
|
||||||
|
{walletDescriptor !== null && (
|
||||||
|
<WalletDescriptorModal
|
||||||
|
open={walletDescriptor !== null}
|
||||||
|
onClose={handleCloseDialog}
|
||||||
|
walletDescriptor={walletDescriptor}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function WalletDescriptorModal({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
walletDescriptor,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
walletDescriptor: ExportBitcoinWalletResponse;
|
||||||
|
}) {
|
||||||
|
const parsedDescriptor = JSON.parse(
|
||||||
|
walletDescriptor.wallet_descriptor["descriptor"],
|
||||||
|
);
|
||||||
|
const stringifiedDescriptor = JSON.stringify(parsedDescriptor, null, 4);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
||||||
|
<DialogTitle>Bitcoin Wallet Descriptor</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
The Bitcoin wallet is derived from your Monero wallet. Opening the
|
||||||
|
same Monero wallet in another Eigenwallet will yield the same Bitcoin
|
||||||
|
wallet.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
It contains your private key. Anyone who has it can spend your funds.
|
||||||
|
It should thus be stored securely.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
It can be imported into other Bitcoin wallets or services that support
|
||||||
|
the descriptor format. For more information on what to do with the
|
||||||
|
descriptor, see our{" "}
|
||||||
|
<Link href={WALLET_DESCRIPTOR_DOCS_URL} target="_blank">
|
||||||
|
documentation
|
||||||
|
</Link>
|
||||||
|
</DialogContentText>
|
||||||
|
<ActionableMonospaceTextBox
|
||||||
|
content={stringifiedDescriptor}
|
||||||
|
displayCopyIcon={true}
|
||||||
|
enableQrCode={false}
|
||||||
|
spoilerText="Press to Reveal Private Key"
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onClose} color="primary" variant="contained">
|
||||||
|
Done
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { Box, Typography, Card } from "@mui/material";
|
||||||
|
import { BitcoinAmount } from "renderer/components/other/Units";
|
||||||
|
import { useAppSelector, useSettings } from "store/hooks";
|
||||||
|
import { satsToBtc } from "utils/conversionUtils";
|
||||||
|
import WalletRefreshButton from "../WalletRefreshButton";
|
||||||
|
|
||||||
|
interface WalletOverviewProps {
|
||||||
|
balance: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FiatBitcoinAmount({ amount }: { amount: number | null }) {
|
||||||
|
const btcPrice = useAppSelector((state) => state.rates.btcPrice);
|
||||||
|
const [fetchFiatPrices, fiatCurrency] = useSettings((settings) => [
|
||||||
|
settings.fetchFiatPrices,
|
||||||
|
settings.fiatCurrency,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!fetchFiatPrices ||
|
||||||
|
fiatCurrency == null ||
|
||||||
|
amount == null ||
|
||||||
|
btcPrice == null
|
||||||
|
) {
|
||||||
|
return <span />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{(amount * btcPrice).toFixed(2)} {fiatCurrency}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WalletOverview({ balance }: WalletOverviewProps) {
|
||||||
|
const btcBalance = balance == null ? null : satsToBtc(balance);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card sx={{ p: 2, position: "relative", borderRadius: 2 }} elevation={4}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "stretch",
|
||||||
|
mb: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Left side content */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||||
|
Available Funds
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h4">
|
||||||
|
<BitcoinAmount amount={btcBalance} />
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
<FiatBitcoinAmount amount={btcBalance} />
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Right side - Refresh button */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<WalletRefreshButton />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
WithdrawBtcResponse,
|
WithdrawBtcResponse,
|
||||||
GetSwapInfoArgs,
|
GetSwapInfoArgs,
|
||||||
ExportBitcoinWalletResponse,
|
ExportBitcoinWalletResponse,
|
||||||
|
GetBitcoinAddressResponse,
|
||||||
CheckMoneroNodeArgs,
|
CheckMoneroNodeArgs,
|
||||||
CheckSeedArgs,
|
CheckSeedArgs,
|
||||||
CheckSeedResponse,
|
CheckSeedResponse,
|
||||||
|
|
@ -49,11 +50,11 @@ import {
|
||||||
ContextStatus,
|
ContextStatus,
|
||||||
} from "models/tauriModel";
|
} from "models/tauriModel";
|
||||||
import {
|
import {
|
||||||
rpcSetBalance,
|
|
||||||
rpcSetSwapInfo,
|
rpcSetSwapInfo,
|
||||||
approvalRequestsReplaced,
|
approvalRequestsReplaced,
|
||||||
contextInitializationFailed,
|
contextInitializationFailed,
|
||||||
} from "store/features/rpcSlice";
|
} from "store/features/rpcSlice";
|
||||||
|
import { setBitcoinBalance } from "store/features/bitcoinWalletSlice";
|
||||||
import {
|
import {
|
||||||
setMainAddress,
|
setMainAddress,
|
||||||
setBalance,
|
setBalance,
|
||||||
|
|
@ -166,7 +167,7 @@ export async function checkBitcoinBalance() {
|
||||||
force_refresh: true,
|
force_refresh: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
store.dispatch(rpcSetBalance(response.balance));
|
store.dispatch(setBitcoinBalance(response.balance));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cheapCheckBitcoinBalance() {
|
export async function cheapCheckBitcoinBalance() {
|
||||||
|
|
@ -174,7 +175,15 @@ export async function cheapCheckBitcoinBalance() {
|
||||||
force_refresh: false,
|
force_refresh: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
store.dispatch(rpcSetBalance(response.balance));
|
store.dispatch(setBitcoinBalance(response.balance));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBitcoinAddress() {
|
||||||
|
const response = await invokeNoArgs<GetBitcoinAddressResponse>(
|
||||||
|
"get_bitcoin_address",
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.address;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllSwapInfos() {
|
export async function getAllSwapInfos() {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import nodesSlice from "./features/nodesSlice";
|
||||||
import conversationsSlice from "./features/conversationsSlice";
|
import conversationsSlice from "./features/conversationsSlice";
|
||||||
import poolSlice from "./features/poolSlice";
|
import poolSlice from "./features/poolSlice";
|
||||||
import walletSlice from "./features/walletSlice";
|
import walletSlice from "./features/walletSlice";
|
||||||
|
import bitcoinWalletSlice from "./features/bitcoinWalletSlice";
|
||||||
import logsSlice from "./features/logsSlice";
|
import logsSlice from "./features/logsSlice";
|
||||||
|
|
||||||
export const reducers = {
|
export const reducers = {
|
||||||
|
|
@ -21,5 +22,6 @@ export const reducers = {
|
||||||
conversations: conversationsSlice,
|
conversations: conversationsSlice,
|
||||||
pool: poolSlice,
|
pool: poolSlice,
|
||||||
wallet: walletSlice,
|
wallet: walletSlice,
|
||||||
|
bitcoinWallet: bitcoinWalletSlice,
|
||||||
logs: logsSlice,
|
logs: logsSlice,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
32
src-gui/src/store/features/bitcoinWalletSlice.ts
Normal file
32
src-gui/src/store/features/bitcoinWalletSlice.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
interface BitcoinWalletState {
|
||||||
|
address: string | null;
|
||||||
|
balance: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: BitcoinWalletState = {
|
||||||
|
address: null,
|
||||||
|
balance: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bitcoinWalletSlice = createSlice({
|
||||||
|
name: "bitcoinWallet",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setBitcoinAddress(state, action: PayloadAction<string>) {
|
||||||
|
state.address = action.payload;
|
||||||
|
},
|
||||||
|
setBitcoinBalance(state, action: PayloadAction<number>) {
|
||||||
|
state.balance = action.payload;
|
||||||
|
},
|
||||||
|
resetBitcoinWalletState(state) {
|
||||||
|
return initialState;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setBitcoinAddress, setBitcoinBalance, resetBitcoinWalletState } =
|
||||||
|
bitcoinWalletSlice.actions;
|
||||||
|
|
||||||
|
export default bitcoinWalletSlice.reducer;
|
||||||
|
|
@ -14,7 +14,6 @@ import { GetSwapInfoResponseExt } from "models/tauriModelExt";
|
||||||
import logger from "utils/logger";
|
import logger from "utils/logger";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
balance: number | null;
|
|
||||||
withdrawTxId: string | null;
|
withdrawTxId: string | null;
|
||||||
rendezvousDiscoveredSellers: (ExtendedMakerStatus | MakerStatus)[];
|
rendezvousDiscoveredSellers: (ExtendedMakerStatus | MakerStatus)[];
|
||||||
swapInfos: {
|
swapInfos: {
|
||||||
|
|
@ -54,7 +53,6 @@ export interface RPCSlice {
|
||||||
const initialState: RPCSlice = {
|
const initialState: RPCSlice = {
|
||||||
status: null,
|
status: null,
|
||||||
state: {
|
state: {
|
||||||
balance: null,
|
|
||||||
withdrawTxId: null,
|
withdrawTxId: null,
|
||||||
rendezvousDiscoveredSellers: [],
|
rendezvousDiscoveredSellers: [],
|
||||||
swapInfos: {},
|
swapInfos: {},
|
||||||
|
|
@ -95,9 +93,6 @@ export const rpcSlice = createSlice({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rpcSetBalance(slice, action: PayloadAction<number>) {
|
|
||||||
slice.state.balance = action.payload;
|
|
||||||
},
|
|
||||||
rpcSetWithdrawTxId(slice, action: PayloadAction<string>) {
|
rpcSetWithdrawTxId(slice, action: PayloadAction<string>) {
|
||||||
slice.state.withdrawTxId = action.payload;
|
slice.state.withdrawTxId = action.payload;
|
||||||
},
|
},
|
||||||
|
|
@ -177,7 +172,6 @@ export const rpcSlice = createSlice({
|
||||||
export const {
|
export const {
|
||||||
contextStatusEventReceived,
|
contextStatusEventReceived,
|
||||||
contextInitializationFailed,
|
contextInitializationFailed,
|
||||||
rpcSetBalance,
|
|
||||||
rpcSetWithdrawTxId,
|
rpcSetWithdrawTxId,
|
||||||
rpcResetWithdrawTxId,
|
rpcResetWithdrawTxId,
|
||||||
rpcSetRendezvousDiscoveredMakers,
|
rpcSetRendezvousDiscoveredMakers,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { throttle, debounce } from "lodash";
|
||||||
import {
|
import {
|
||||||
getAllSwapInfos,
|
getAllSwapInfos,
|
||||||
checkBitcoinBalance,
|
checkBitcoinBalance,
|
||||||
|
getBitcoinAddress,
|
||||||
updateAllNodeStatuses,
|
updateAllNodeStatuses,
|
||||||
fetchSellersAtPresetRendezvousPoints,
|
fetchSellersAtPresetRendezvousPoints,
|
||||||
getSwapInfo,
|
getSwapInfo,
|
||||||
|
|
@ -30,6 +31,7 @@ import {
|
||||||
addFeedbackId,
|
addFeedbackId,
|
||||||
setConversation,
|
setConversation,
|
||||||
} from "store/features/conversationsSlice";
|
} from "store/features/conversationsSlice";
|
||||||
|
import { setBitcoinAddress } from "store/features/bitcoinWalletSlice";
|
||||||
|
|
||||||
// Create a Map to store throttled functions per swap_id
|
// Create a Map to store throttled functions per swap_id
|
||||||
const throttledGetSwapInfoFunctions = new Map<
|
const throttledGetSwapInfoFunctions = new Map<
|
||||||
|
|
@ -86,15 +88,21 @@ export function createMainListeners() {
|
||||||
|
|
||||||
if (!status) return;
|
if (!status) return;
|
||||||
|
|
||||||
// If the Bitcoin wallet just came available, check the Bitcoin balance
|
// If the Bitcoin wallet just came available, check the Bitcoin balance and get address
|
||||||
if (
|
if (
|
||||||
status.bitcoin_wallet_available &&
|
status.bitcoin_wallet_available &&
|
||||||
!previousContextStatus?.bitcoin_wallet_available
|
!previousContextStatus?.bitcoin_wallet_available
|
||||||
) {
|
) {
|
||||||
logger.info(
|
logger.info(
|
||||||
"Bitcoin wallet just became available, checking balance...",
|
"Bitcoin wallet just became available, checking balance and getting address...",
|
||||||
);
|
);
|
||||||
await checkBitcoinBalance();
|
await checkBitcoinBalance();
|
||||||
|
try {
|
||||||
|
const address = await getBitcoinAddress();
|
||||||
|
store.dispatch(setBitcoinAddress(address));
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to fetch Bitcoin address", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the Monero wallet just came available, initialize the Monero wallet
|
// If the Monero wallet just came available, initialize the Monero wallet
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ use swap::cli::{
|
||||||
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, ChangeMoneroNodeArgs,
|
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, ChangeMoneroNodeArgs,
|
||||||
CheckElectrumNodeArgs, CheckElectrumNodeResponse, CheckMoneroNodeArgs,
|
CheckElectrumNodeArgs, CheckElectrumNodeResponse, CheckMoneroNodeArgs,
|
||||||
CheckMoneroNodeResponse, CheckSeedArgs, CheckSeedResponse, DfxAuthenticateResponse,
|
CheckMoneroNodeResponse, CheckSeedArgs, CheckSeedResponse, DfxAuthenticateResponse,
|
||||||
ExportBitcoinWalletArgs, GetCurrentSwapArgs, GetDataDirArgs, GetHistoryArgs,
|
ExportBitcoinWalletArgs, GetBitcoinAddressArgs, GetCurrentSwapArgs, GetDataDirArgs,
|
||||||
GetLogsArgs, GetMoneroAddressesArgs, GetMoneroBalanceArgs, GetMoneroHistoryArgs,
|
GetHistoryArgs, GetLogsArgs, GetMoneroAddressesArgs, GetMoneroBalanceArgs,
|
||||||
GetMoneroMainAddressArgs, GetMoneroSeedArgs, GetMoneroSyncProgressArgs,
|
GetMoneroHistoryArgs, GetMoneroMainAddressArgs, GetMoneroSeedArgs,
|
||||||
GetPendingApprovalsResponse, GetRestoreHeightArgs, GetSwapInfoArgs,
|
GetMoneroSyncProgressArgs, GetPendingApprovalsResponse, GetRestoreHeightArgs,
|
||||||
GetSwapInfosAllArgs, ListSellersArgs, MoneroRecoveryArgs, RedactArgs,
|
GetSwapInfoArgs, GetSwapInfosAllArgs, ListSellersArgs, MoneroRecoveryArgs, RedactArgs,
|
||||||
RejectApprovalArgs, RejectApprovalResponse, ResolveApprovalArgs, ResumeSwapArgs,
|
RejectApprovalArgs, RejectApprovalResponse, ResolveApprovalArgs, ResumeSwapArgs,
|
||||||
SendMoneroArgs, SetRestoreHeightArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs,
|
SendMoneroArgs, SetRestoreHeightArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs,
|
||||||
},
|
},
|
||||||
|
|
@ -35,6 +35,7 @@ macro_rules! generate_command_handlers {
|
||||||
() => {
|
() => {
|
||||||
tauri::generate_handler![
|
tauri::generate_handler![
|
||||||
get_balance,
|
get_balance,
|
||||||
|
get_bitcoin_address,
|
||||||
get_monero_addresses,
|
get_monero_addresses,
|
||||||
get_swap_info,
|
get_swap_info,
|
||||||
get_swap_infos_all,
|
get_swap_infos_all,
|
||||||
|
|
@ -435,6 +436,7 @@ tauri_command!(send_monero, SendMoneroArgs);
|
||||||
tauri_command!(change_monero_node, ChangeMoneroNodeArgs);
|
tauri_command!(change_monero_node, ChangeMoneroNodeArgs);
|
||||||
|
|
||||||
// These commands require no arguments
|
// These commands require no arguments
|
||||||
|
tauri_command!(get_bitcoin_address, GetBitcoinAddressArgs, no_args);
|
||||||
tauri_command!(get_wallet_descriptor, ExportBitcoinWalletArgs, no_args);
|
tauri_command!(get_wallet_descriptor, ExportBitcoinWalletArgs, no_args);
|
||||||
tauri_command!(suspend_current_swap, SuspendCurrentSwapArgs, no_args);
|
tauri_command!(suspend_current_swap, SuspendCurrentSwapArgs, no_args);
|
||||||
tauri_command!(get_swap_info, GetSwapInfoArgs);
|
tauri_command!(get_swap_info, GetSwapInfoArgs);
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,30 @@ impl Request for BalanceArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBitcoinAddress
|
||||||
|
#[typeshare]
|
||||||
|
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct GetBitcoinAddressArgs;
|
||||||
|
|
||||||
|
#[typeshare]
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct GetBitcoinAddressResponse {
|
||||||
|
#[typeshare(serialized_as = "string")]
|
||||||
|
#[serde(with = "swap_serde::bitcoin::address_serde")]
|
||||||
|
pub address: bitcoin::Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request for GetBitcoinAddressArgs {
|
||||||
|
type Response = GetBitcoinAddressResponse;
|
||||||
|
|
||||||
|
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||||
|
let bitcoin_wallet = ctx.try_get_bitcoin_wallet().await?;
|
||||||
|
let address = bitcoin_wallet.new_address().await?;
|
||||||
|
|
||||||
|
Ok(GetBitcoinAddressResponse { address })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetHistory
|
// GetHistory
|
||||||
#[typeshare]
|
#[typeshare]
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue