mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-05-01 05:06:16 -04:00
feat(gui): Add button to display monero redeem recovery keys (#64)
This commit is contained in:
parent
e4bddd2287
commit
177e3e9949
@ -11,22 +11,22 @@ import { ReactNode, useState } from "react";
|
|||||||
import { useIsContextAvailable } from "store/hooks";
|
import { useIsContextAvailable } from "store/hooks";
|
||||||
|
|
||||||
interface PromiseInvokeButtonProps<T> {
|
interface PromiseInvokeButtonProps<T> {
|
||||||
onSuccess: (data: T) => void | null;
|
onSuccess?: (data: T) => void | null;
|
||||||
onClick: () => Promise<T>;
|
onInvoke: () => Promise<T>;
|
||||||
onPendingChange: (isPending: boolean) => void | null;
|
onPendingChange?: (isPending: boolean) => void | null;
|
||||||
isLoadingOverride: boolean;
|
isLoadingOverride?: boolean;
|
||||||
isIconButton: boolean;
|
isIconButton?: boolean;
|
||||||
loadIcon: ReactNode;
|
loadIcon?: ReactNode;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
displayErrorSnackbar: boolean;
|
displayErrorSnackbar?: boolean;
|
||||||
tooltipTitle: string | null;
|
tooltipTitle?: string | null;
|
||||||
requiresContext: boolean;
|
requiresContext?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PromiseInvokeButton<T>({
|
export default function PromiseInvokeButton<T>({
|
||||||
disabled = false,
|
disabled = false,
|
||||||
onSuccess = null,
|
onSuccess = null,
|
||||||
onClick,
|
onInvoke,
|
||||||
endIcon,
|
endIcon,
|
||||||
loadIcon = null,
|
loadIcon = null,
|
||||||
isLoadingOverride = false,
|
isLoadingOverride = false,
|
||||||
@ -36,7 +36,7 @@ export default function PromiseInvokeButton<T>({
|
|||||||
requiresContext = true,
|
requiresContext = true,
|
||||||
tooltipTitle = null,
|
tooltipTitle = null,
|
||||||
...rest
|
...rest
|
||||||
}: ButtonProps & PromiseInvokeButtonProps<T>) {
|
}: PromiseInvokeButtonProps<T> & ButtonProps) {
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
const isContextAvailable = useIsContextAvailable();
|
const isContextAvailable = useIsContextAvailable();
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ export default function PromiseInvokeButton<T>({
|
|||||||
try {
|
try {
|
||||||
onPendingChange?.(true);
|
onPendingChange?.(true);
|
||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
const result = await onClick();
|
const result = await onInvoke();
|
||||||
onSuccess?.(result);
|
onSuccess?.(result);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (displayErrorSnackbar) {
|
if (displayErrorSnackbar) {
|
||||||
|
@ -33,7 +33,7 @@ export default function SwapSuspendAlert({
|
|||||||
<PromiseInvokeButton
|
<PromiseInvokeButton
|
||||||
color="primary"
|
color="primary"
|
||||||
onSuccess={onClose}
|
onSuccess={onClose}
|
||||||
onClick={suspendCurrentSwap}
|
onInvoke={suspendCurrentSwap}
|
||||||
>
|
>
|
||||||
Force stop
|
Force stop
|
||||||
</PromiseInvokeButton>
|
</PromiseInvokeButton>
|
||||||
|
@ -124,7 +124,7 @@ export default function ListSellersDialog({
|
|||||||
disabled={!(rendezvousAddress && !getMultiAddressError())}
|
disabled={!(rendezvousAddress && !getMultiAddressError())}
|
||||||
color="primary"
|
color="primary"
|
||||||
onSuccess={handleSuccess}
|
onSuccess={handleSuccess}
|
||||||
onClick={() => {
|
onInvoke={() => {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -71,7 +71,7 @@ export default function InitPage() {
|
|||||||
size="large"
|
size="large"
|
||||||
className={classes.initButton}
|
className={classes.initButton}
|
||||||
endIcon={<PlayArrowIcon />}
|
endIcon={<PlayArrowIcon />}
|
||||||
onClick={init}
|
onInvoke={init}
|
||||||
displayErrorSnackbar
|
displayErrorSnackbar
|
||||||
>
|
>
|
||||||
Start swap
|
Start swap
|
||||||
|
@ -51,7 +51,7 @@ export default function WithdrawDialog({
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={!withdrawAddressValid}
|
disabled={!withdrawAddressValid}
|
||||||
onClick={() => withdrawBtc(withdrawAddress)}
|
onInvoke={() => withdrawBtc(withdrawAddress)}
|
||||||
onPendingChange={(pending) => {
|
onPendingChange={(pending) => {
|
||||||
console.log("pending", pending);
|
console.log("pending", pending);
|
||||||
setPending(pending);
|
setPending(pending);
|
||||||
|
@ -36,7 +36,7 @@ export default function RpcControlBox() {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
endIcon={<PlayArrowIcon />}
|
endIcon={<PlayArrowIcon />}
|
||||||
disabled={isRunning}
|
disabled={isRunning}
|
||||||
onClick={() => {
|
onInvoke={() => {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -46,7 +46,7 @@ export default function RpcControlBox() {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
endIcon={<StopIcon />}
|
endIcon={<StopIcon />}
|
||||||
disabled={!isRunning}
|
disabled={!isRunning}
|
||||||
onClick={() => {
|
onInvoke={() => {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -57,7 +57,7 @@ export default function RpcControlBox() {
|
|||||||
isIconButton
|
isIconButton
|
||||||
size="small"
|
size="small"
|
||||||
tooltipTitle="Open the data directory of the Swap Daemon in your file explorer"
|
tooltipTitle="Open the data directory of the Swap Daemon in your file explorer"
|
||||||
onClick={() => {
|
onInvoke={() => {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -46,7 +46,7 @@ export default function TorInfoBox() {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={isTorRunning}
|
disabled={isTorRunning}
|
||||||
endIcon={<PlayArrowIcon />}
|
endIcon={<PlayArrowIcon />}
|
||||||
onClick={() => {
|
onInvoke={() => {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -56,7 +56,7 @@ export default function TorInfoBox() {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={!isTorRunning}
|
disabled={!isTorRunning}
|
||||||
endIcon={<StopIcon />}
|
endIcon={<StopIcon />}
|
||||||
onClick={() => {
|
onInvoke={() => {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -24,7 +24,7 @@ export function SwapResumeButton({
|
|||||||
color="primary"
|
color="primary"
|
||||||
disabled={swap.completed}
|
disabled={swap.completed}
|
||||||
endIcon={<PlayArrowIcon />}
|
endIcon={<PlayArrowIcon />}
|
||||||
onClick={() => resumeSwap(swap.swap_id)}
|
onInvoke={() => resumeSwap(swap.swap_id)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
Resume
|
Resume
|
||||||
@ -48,7 +48,7 @@ export function SwapCancelRefundButton({
|
|||||||
<PromiseInvokeButton
|
<PromiseInvokeButton
|
||||||
displayErrorSnackbar={false}
|
displayErrorSnackbar={false}
|
||||||
{...props}
|
{...props}
|
||||||
onClick={async () => {
|
onInvoke={async () => {
|
||||||
// TODO: Implement this using the Tauri RPC
|
// TODO: Implement this using the Tauri RPC
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}}
|
}}
|
||||||
|
@ -23,7 +23,7 @@ export default function SwapLogFileOpenButton({
|
|||||||
onSuccess={(data) => {
|
onSuccess={(data) => {
|
||||||
setLogs(data as CliLog[]);
|
setLogs(data as CliLog[]);
|
||||||
}}
|
}}
|
||||||
onClick={async () => {
|
onInvoke={async () => {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -8,16 +8,22 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { ButtonProps } from "@material-ui/core/Button/Button";
|
import { ButtonProps } from "@material-ui/core/Button/Button";
|
||||||
import { GetSwapInfoArgs } from "models/tauriModel";
|
import { BobStateName, GetSwapInfoResponseExt } from "models/tauriModelExt";
|
||||||
import { rpcResetMoneroRecoveryKeys } from "store/features/rpcSlice";
|
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||||
|
import { getMoneroRecoveryKeys } from "renderer/rpc";
|
||||||
|
import { store } from "renderer/store/storeRenderer";
|
||||||
|
import {
|
||||||
|
rpcResetMoneroRecoveryKeys,
|
||||||
|
rpcSetMoneroRecoveryKeys,
|
||||||
|
} from "store/features/rpcSlice";
|
||||||
import { useAppDispatch, useAppSelector } from "store/hooks";
|
import { useAppDispatch, useAppSelector } from "store/hooks";
|
||||||
import DialogHeader from "../../../modal/DialogHeader";
|
import DialogHeader from "../../../modal/DialogHeader";
|
||||||
import ScrollablePaperTextBox from "../../../other/ScrollablePaperTextBox";
|
import ScrollablePaperTextBox from "../../../other/ScrollablePaperTextBox";
|
||||||
|
|
||||||
function MoneroRecoveryKeysDialog() {
|
function MoneroRecoveryKeysDialog({
|
||||||
// TODO: Reimplement this using the new Tauri API
|
swap_id,
|
||||||
return null;
|
...rest
|
||||||
|
}: GetSwapInfoResponseExt) {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const keys = useAppSelector((s) => s.rpc.state.moneroRecovery);
|
const keys = useAppSelector((s) => s.rpc.state.moneroRecovery);
|
||||||
|
|
||||||
@ -25,14 +31,14 @@ function MoneroRecoveryKeysDialog() {
|
|||||||
dispatch(rpcResetMoneroRecoveryKeys());
|
dispatch(rpcResetMoneroRecoveryKeys());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keys === null || keys.swapId !== swap.swap_id) {
|
if (keys === null || keys.swapId !== swap_id) {
|
||||||
return <></>;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onClose={onClose} maxWidth="sm" fullWidth>
|
<Dialog open onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
<DialogHeader
|
<DialogHeader
|
||||||
title={`Recovery Keys for swap ${swap.swap_id.substring(0, 5)}...`}
|
title={`Recovery Keys for swap ${swap_id.substring(0, 5)}...`}
|
||||||
/>
|
/>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
@ -40,7 +46,7 @@ function MoneroRecoveryKeysDialog() {
|
|||||||
the multi-signature wallet.
|
the multi-signature wallet.
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
This is useful if the swap daemon fails to redeem the funds itself
|
This is useful if the application fails to redeem the funds itself
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
If you have come this far, there is no risk of losing funds. You
|
If you have come this far, there is no risk of losing funds. You
|
||||||
@ -79,6 +85,7 @@ function MoneroRecoveryKeysDialog() {
|
|||||||
title={title}
|
title={title}
|
||||||
copyValue={value}
|
copyValue={value}
|
||||||
rows={[value]}
|
rows={[value]}
|
||||||
|
key={title}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
@ -95,11 +102,8 @@ function MoneroRecoveryKeysDialog() {
|
|||||||
export function SwapMoneroRecoveryButton({
|
export function SwapMoneroRecoveryButton({
|
||||||
swap,
|
swap,
|
||||||
...props
|
...props
|
||||||
}: { swap: GetSwapInfoArgs } & ButtonProps) {
|
}: { swap: GetSwapInfoResponseExt } & ButtonProps) {
|
||||||
return <> </>;
|
const isRecoverable = swap.state_name === BobStateName.BtcRedeemed;
|
||||||
/* TODO: Reimplement this using the new Tauri API
|
|
||||||
const isRecoverable = isSwapMoneroRecoverable(swap.state_name);
|
|
||||||
|
|
||||||
|
|
||||||
if (!isRecoverable) {
|
if (!isRecoverable) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@ -108,15 +112,15 @@ export function SwapMoneroRecoveryButton({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PromiseInvokeButton
|
<PromiseInvokeButton
|
||||||
onClick={async () => {
|
onInvoke={() => getMoneroRecoveryKeys(swap.swap_id)}
|
||||||
throw new Error("Not implemented");
|
onSuccess={(keys) =>
|
||||||
}}
|
store.dispatch(rpcSetMoneroRecoveryKeys([swap.swap_id, keys]))
|
||||||
|
}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
Display Monero Recovery Keys
|
Display Monero Recovery Keys
|
||||||
</PromiseInvokeButton>
|
</PromiseInvokeButton>
|
||||||
<MoneroRecoveryKeysDialog swap={swap} />
|
<MoneroRecoveryKeysDialog {...swap} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ export default function WalletRefreshButton() {
|
|||||||
<PromiseInvokeButton
|
<PromiseInvokeButton
|
||||||
endIcon={<RefreshIcon />}
|
endIcon={<RefreshIcon />}
|
||||||
isIconButton
|
isIconButton
|
||||||
onClick={() => checkBitcoinBalance()}
|
onInvoke={() => checkBitcoinBalance()}
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
BuyXmrArgs,
|
BuyXmrArgs,
|
||||||
BuyXmrResponse,
|
BuyXmrResponse,
|
||||||
GetSwapInfoResponse,
|
GetSwapInfoResponse,
|
||||||
|
MoneroRecoveryArgs,
|
||||||
ResumeSwapArgs,
|
ResumeSwapArgs,
|
||||||
ResumeSwapResponse,
|
ResumeSwapResponse,
|
||||||
SuspendCurrentSwapResponse,
|
SuspendCurrentSwapResponse,
|
||||||
@ -23,8 +24,16 @@ import { swapTauriEventReceived } from "store/features/swapSlice";
|
|||||||
import { store } from "./store/storeRenderer";
|
import { store } from "./store/storeRenderer";
|
||||||
import { Provider } from "models/apiModel";
|
import { Provider } from "models/apiModel";
|
||||||
import { providerToConcatenatedMultiAddr } from "utils/multiAddrUtils";
|
import { providerToConcatenatedMultiAddr } from "utils/multiAddrUtils";
|
||||||
|
import { MoneroRecoveryResponse } from "models/rpcModel";
|
||||||
|
|
||||||
export async function initEventListeners() {
|
export async function initEventListeners() {
|
||||||
|
// This operation is in-expensive
|
||||||
|
// We do this in case we miss the context init progress event because the frontend took too long to load
|
||||||
|
// TOOD: Replace this with a more reliable mechanism (such as an event replay mechanism)
|
||||||
|
if (await checkContextAvailability()) {
|
||||||
|
store.dispatch(contextStatusEventReceived({ type: "Available" }));
|
||||||
|
}
|
||||||
|
|
||||||
listen<TauriSwapProgressEventWrapper>("swap-progress-update", (event) => {
|
listen<TauriSwapProgressEventWrapper>("swap-progress-update", (event) => {
|
||||||
console.log("Received swap progress event", event.payload);
|
console.log("Received swap progress event", event.payload);
|
||||||
store.dispatch(swapTauriEventReceived(event.payload));
|
store.dispatch(swapTauriEventReceived(event.payload));
|
||||||
@ -99,3 +108,19 @@ export async function resumeSwap(swapId: string) {
|
|||||||
export async function suspendCurrentSwap() {
|
export async function suspendCurrentSwap() {
|
||||||
await invokeNoArgs<SuspendCurrentSwapResponse>("suspend_current_swap");
|
await invokeNoArgs<SuspendCurrentSwapResponse>("suspend_current_swap");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getMoneroRecoveryKeys(
|
||||||
|
swapId: string,
|
||||||
|
): Promise<MoneroRecoveryResponse> {
|
||||||
|
return await invoke<MoneroRecoveryArgs, MoneroRecoveryResponse>(
|
||||||
|
"monero_recovery",
|
||||||
|
{
|
||||||
|
swap_id: swapId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkContextAvailability(): Promise<boolean> {
|
||||||
|
const available = await invokeNoArgs<boolean>("is_context_available");
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
@ -56,6 +56,7 @@ export function useAllProviders() {
|
|||||||
|
|
||||||
export function useSwapInfosSortedByDate() {
|
export function useSwapInfosSortedByDate() {
|
||||||
const swapInfos = useAppSelector((state) => state.rpc.state.swapInfos);
|
const swapInfos = useAppSelector((state) => state.rpc.state.swapInfos);
|
||||||
|
|
||||||
return sortBy(
|
return sortBy(
|
||||||
Object.values(swapInfos),
|
Object.values(swapInfos),
|
||||||
(swap) => -parseDateString(swap.start_date),
|
(swap) => -parseDateString(swap.start_date),
|
||||||
|
@ -3,8 +3,8 @@ use std::sync::Arc;
|
|||||||
use swap::cli::{
|
use swap::cli::{
|
||||||
api::{
|
api::{
|
||||||
request::{
|
request::{
|
||||||
BalanceArgs, BuyXmrArgs, GetHistoryArgs, GetSwapInfosAllArgs, ResumeSwapArgs,
|
BalanceArgs, BuyXmrArgs, GetHistoryArgs, GetSwapInfosAllArgs, MoneroRecoveryArgs,
|
||||||
SuspendCurrentSwapArgs, WithdrawBtcArgs,
|
ResumeSwapArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs,
|
||||||
},
|
},
|
||||||
tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle},
|
tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle},
|
||||||
Context, ContextBuilder,
|
Context, ContextBuilder,
|
||||||
@ -167,7 +167,9 @@ pub fn run() {
|
|||||||
buy_xmr,
|
buy_xmr,
|
||||||
resume_swap,
|
resume_swap,
|
||||||
get_history,
|
get_history,
|
||||||
suspend_current_swap
|
monero_recovery,
|
||||||
|
suspend_current_swap,
|
||||||
|
is_context_available,
|
||||||
])
|
])
|
||||||
.setup(setup)
|
.setup(setup)
|
||||||
.build(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
@ -203,6 +205,15 @@ tauri_command!(get_balance, BalanceArgs);
|
|||||||
tauri_command!(buy_xmr, BuyXmrArgs);
|
tauri_command!(buy_xmr, BuyXmrArgs);
|
||||||
tauri_command!(resume_swap, ResumeSwapArgs);
|
tauri_command!(resume_swap, ResumeSwapArgs);
|
||||||
tauri_command!(withdraw_btc, WithdrawBtcArgs);
|
tauri_command!(withdraw_btc, WithdrawBtcArgs);
|
||||||
|
tauri_command!(monero_recovery, MoneroRecoveryArgs);
|
||||||
|
|
||||||
|
// These commands require no arguments
|
||||||
tauri_command!(suspend_current_swap, SuspendCurrentSwapArgs, no_args);
|
tauri_command!(suspend_current_swap, SuspendCurrentSwapArgs, no_args);
|
||||||
tauri_command!(get_swap_infos_all, GetSwapInfosAllArgs, no_args);
|
tauri_command!(get_swap_infos_all, GetSwapInfosAllArgs, no_args);
|
||||||
tauri_command!(get_history, GetHistoryArgs, no_args);
|
tauri_command!(get_history, GetHistoryArgs, no_args);
|
||||||
|
|
||||||
|
/// Here we define Tauri commands whose implementation is not delegated to the Request trait
|
||||||
|
#[tauri::command]
|
||||||
|
async fn is_context_available(context: tauri::State<'_, RwLock<State>>) -> Result<bool, String> {
|
||||||
|
Ok(context.read().await.try_get_context().is_ok())
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user