feat(tauri): Allow export of wallet descriptors (#118)

This commit is contained in:
Einliterflasche 2024-10-15 14:22:35 +02:00 committed by GitHub
parent 898c7a2450
commit c91adb3ac8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 141 additions and 11 deletions

View file

@ -0,0 +1,115 @@
import {
Box,
Typography,
makeStyles,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Link,
DialogContentText,
} from "@material-ui/core";
import InfoBox from "renderer/components/modal/swap/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";
const useStyles = makeStyles((theme) => ({
content: {
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
gap: theme.spacing(2),
}
}));
export default function ExportDataBox() {
const classes = useStyles();
const [walletDescriptor, setWalletDescriptor] = useState<ExportBitcoinWalletResponse | null>(null);
const handleCloseDialog = () => {
setWalletDescriptor(null);
};
return (
<InfoBox
title="Export Bitcoin Wallet"
icon={null}
loading={false}
mainContent={
<Box className={classes.content}>
<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}
>
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/UnstoppableSwap/core/blob/master/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>
);
};

View file

@ -11,10 +11,9 @@ export default function FeedbackInfoBox() {
title="Feedback" title="Feedback"
mainContent={ mainContent={
<Typography variant="subtitle2"> <Typography variant="subtitle2">
The main goal of this project is to make Atomic Swaps easier to use, Your input is crucial to us! We'd love to hear your thoughts on
and for that we need genuine users&apos; input. Please leave some Atomic Swaps. We personally read every response to improve the
feedback, it takes just two minutes. I&apos;ll read each and every project. Got two minutes to share?
survey response and take your feedback into consideration.
</Typography> </Typography>
} }
additionalContent={ additionalContent={

View file

@ -4,7 +4,7 @@ import DonateInfoBox from "./DonateInfoBox";
import FeedbackInfoBox from "./FeedbackInfoBox"; import FeedbackInfoBox from "./FeedbackInfoBox";
import DaemonControlBox from "./DaemonControlBox"; import DaemonControlBox from "./DaemonControlBox";
import SettingsBox from "./SettingsBox"; import SettingsBox from "./SettingsBox";
import ExportDataBox from "./ExportDataBox";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
outer: { outer: {
display: "flex", display: "flex",
@ -19,9 +19,10 @@ export default function HelpPage() {
return ( return (
<Box className={classes.outer}> <Box className={classes.outer}>
<FeedbackInfoBox />
<DaemonControlBox /> <DaemonControlBox />
<SettingsBox /> <SettingsBox />
<FeedbackInfoBox /> <ExportDataBox />
<ContactInfoBox /> <ContactInfoBox />
<DonateInfoBox /> <DonateInfoBox />
</Box> </Box>

View file

@ -21,6 +21,7 @@ import {
TauriDatabaseStateEvent, TauriDatabaseStateEvent,
TauriTimelockChangeEvent, TauriTimelockChangeEvent,
GetSwapInfoArgs, GetSwapInfoArgs,
ExportBitcoinWalletResponse,
} from "models/tauriModel"; } from "models/tauriModel";
import { import {
contextStatusEventReceived, contextStatusEventReceived,
@ -214,3 +215,7 @@ export async function initializeContext() {
testnet, testnet,
}); });
} }
export async function getWalletDescriptor() {
return await invokeNoArgs<ExportBitcoinWalletResponse>("get_wallet_descriptor");
}

View file

@ -4,8 +4,8 @@ use std::sync::Arc;
use swap::cli::{ use swap::cli::{
api::{ api::{
request::{ request::{
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, GetHistoryArgs, GetLogsArgs, BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, ExportBitcoinWalletArgs, GetHistoryArgs,
GetSwapInfoArgs, GetSwapInfosAllArgs, ListSellersArgs, MoneroRecoveryArgs, GetLogsArgs, GetSwapInfoArgs, GetSwapInfosAllArgs, ListSellersArgs, MoneroRecoveryArgs,
ResumeSwapArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs, ResumeSwapArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs,
}, },
tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle, TauriSettings}, tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle, TauriSettings},
@ -146,6 +146,7 @@ pub fn run() {
cancel_and_refund, cancel_and_refund,
is_context_available, is_context_available,
initialize_context, initialize_context,
get_wallet_descriptor,
]) ])
.setup(setup) .setup(setup)
.build(tauri::generate_context!()) .build(tauri::generate_context!())
@ -186,8 +187,8 @@ tauri_command!(monero_recovery, MoneroRecoveryArgs);
tauri_command!(get_logs, GetLogsArgs); tauri_command!(get_logs, GetLogsArgs);
tauri_command!(list_sellers, ListSellersArgs); tauri_command!(list_sellers, ListSellersArgs);
tauri_command!(cancel_and_refund, CancelAndRefundArgs); tauri_command!(cancel_and_refund, CancelAndRefundArgs);
// These commands require no arguments // These commands require no arguments
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);
tauri_command!(get_swap_infos_all, GetSwapInfosAllArgs, no_args); tauri_command!(get_swap_infos_all, GetSwapInfosAllArgs, no_args);

View file

@ -346,13 +346,22 @@ impl Request for GetConfig {
} }
} }
#[typeshare]
#[derive(Serialize, Deserialize, Debug)]
pub struct ExportBitcoinWalletArgs; pub struct ExportBitcoinWalletArgs;
#[typeshare]
#[derive(Serialize, Deserialize, Debug)]
pub struct ExportBitcoinWalletResponse {
pub wallet_descriptor: serde_json::Value,
}
impl Request for ExportBitcoinWalletArgs { impl Request for ExportBitcoinWalletArgs {
type Response = serde_json::Value; type Response = ExportBitcoinWalletResponse;
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> { async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
export_bitcoin_wallet(ctx).await let wallet_descriptor = export_bitcoin_wallet(ctx).await?;
Ok(ExportBitcoinWalletResponse { wallet_descriptor })
} }
} }