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";
|
||||
|
||||
interface PromiseInvokeButtonProps<T> {
|
||||
onSuccess: (data: T) => void | null;
|
||||
onClick: () => Promise<T>;
|
||||
onPendingChange: (isPending: boolean) => void | null;
|
||||
isLoadingOverride: boolean;
|
||||
isIconButton: boolean;
|
||||
loadIcon: ReactNode;
|
||||
disabled: boolean;
|
||||
displayErrorSnackbar: boolean;
|
||||
tooltipTitle: string | null;
|
||||
requiresContext: boolean;
|
||||
onSuccess?: (data: T) => void | null;
|
||||
onInvoke: () => Promise<T>;
|
||||
onPendingChange?: (isPending: boolean) => void | null;
|
||||
isLoadingOverride?: boolean;
|
||||
isIconButton?: boolean;
|
||||
loadIcon?: ReactNode;
|
||||
disabled?: boolean;
|
||||
displayErrorSnackbar?: boolean;
|
||||
tooltipTitle?: string | null;
|
||||
requiresContext?: boolean;
|
||||
}
|
||||
|
||||
export default function PromiseInvokeButton<T>({
|
||||
disabled = false,
|
||||
onSuccess = null,
|
||||
onClick,
|
||||
onInvoke,
|
||||
endIcon,
|
||||
loadIcon = null,
|
||||
isLoadingOverride = false,
|
||||
@ -36,7 +36,7 @@ export default function PromiseInvokeButton<T>({
|
||||
requiresContext = true,
|
||||
tooltipTitle = null,
|
||||
...rest
|
||||
}: ButtonProps & PromiseInvokeButtonProps<T>) {
|
||||
}: PromiseInvokeButtonProps<T> & ButtonProps) {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const isContextAvailable = useIsContextAvailable();
|
||||
|
||||
@ -52,7 +52,7 @@ export default function PromiseInvokeButton<T>({
|
||||
try {
|
||||
onPendingChange?.(true);
|
||||
setIsPending(true);
|
||||
const result = await onClick();
|
||||
const result = await onInvoke();
|
||||
onSuccess?.(result);
|
||||
} catch (e: unknown) {
|
||||
if (displayErrorSnackbar) {
|
||||
|
@ -33,7 +33,7 @@ export default function SwapSuspendAlert({
|
||||
<PromiseInvokeButton
|
||||
color="primary"
|
||||
onSuccess={onClose}
|
||||
onClick={suspendCurrentSwap}
|
||||
onInvoke={suspendCurrentSwap}
|
||||
>
|
||||
Force stop
|
||||
</PromiseInvokeButton>
|
||||
|
@ -124,7 +124,7 @@ export default function ListSellersDialog({
|
||||
disabled={!(rendezvousAddress && !getMultiAddressError())}
|
||||
color="primary"
|
||||
onSuccess={handleSuccess}
|
||||
onClick={() => {
|
||||
onInvoke={() => {
|
||||
throw new Error("Not implemented");
|
||||
}}
|
||||
>
|
||||
|
@ -71,7 +71,7 @@ export default function InitPage() {
|
||||
size="large"
|
||||
className={classes.initButton}
|
||||
endIcon={<PlayArrowIcon />}
|
||||
onClick={init}
|
||||
onInvoke={init}
|
||||
displayErrorSnackbar
|
||||
>
|
||||
Start swap
|
||||
|
@ -51,7 +51,7 @@ export default function WithdrawDialog({
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={!withdrawAddressValid}
|
||||
onClick={() => withdrawBtc(withdrawAddress)}
|
||||
onInvoke={() => withdrawBtc(withdrawAddress)}
|
||||
onPendingChange={(pending) => {
|
||||
console.log("pending", pending);
|
||||
setPending(pending);
|
||||
|
@ -36,7 +36,7 @@ export default function RpcControlBox() {
|
||||
variant="contained"
|
||||
endIcon={<PlayArrowIcon />}
|
||||
disabled={isRunning}
|
||||
onClick={() => {
|
||||
onInvoke={() => {
|
||||
throw new Error("Not implemented");
|
||||
}}
|
||||
>
|
||||
@ -46,7 +46,7 @@ export default function RpcControlBox() {
|
||||
variant="contained"
|
||||
endIcon={<StopIcon />}
|
||||
disabled={!isRunning}
|
||||
onClick={() => {
|
||||
onInvoke={() => {
|
||||
throw new Error("Not implemented");
|
||||
}}
|
||||
>
|
||||
@ -57,7 +57,7 @@ export default function RpcControlBox() {
|
||||
isIconButton
|
||||
size="small"
|
||||
tooltipTitle="Open the data directory of the Swap Daemon in your file explorer"
|
||||
onClick={() => {
|
||||
onInvoke={() => {
|
||||
throw new Error("Not implemented");
|
||||
}}
|
||||
/>
|
||||
|
@ -46,7 +46,7 @@ export default function TorInfoBox() {
|
||||
variant="contained"
|
||||
disabled={isTorRunning}
|
||||
endIcon={<PlayArrowIcon />}
|
||||
onClick={() => {
|
||||
onInvoke={() => {
|
||||
throw new Error("Not implemented");
|
||||
}}
|
||||
>
|
||||
@ -56,7 +56,7 @@ export default function TorInfoBox() {
|
||||
variant="contained"
|
||||
disabled={!isTorRunning}
|
||||
endIcon={<StopIcon />}
|
||||
onClick={() => {
|
||||
onInvoke={() => {
|
||||
throw new Error("Not implemented");
|
||||
}}
|
||||
>
|
||||
|
@ -24,7 +24,7 @@ export function SwapResumeButton({
|
||||
color="primary"
|
||||
disabled={swap.completed}
|
||||
endIcon={<PlayArrowIcon />}
|
||||
onClick={() => resumeSwap(swap.swap_id)}
|
||||
onInvoke={() => resumeSwap(swap.swap_id)}
|
||||
{...props}
|
||||
>
|
||||
Resume
|
||||
@ -48,7 +48,7 @@ export function SwapCancelRefundButton({
|
||||
<PromiseInvokeButton
|
||||
displayErrorSnackbar={false}
|
||||
{...props}
|
||||
onClick={async () => {
|
||||
onInvoke={async () => {
|
||||
// TODO: Implement this using the Tauri RPC
|
||||
throw new Error("Not implemented");
|
||||
}}
|
||||
|
@ -23,7 +23,7 @@ export default function SwapLogFileOpenButton({
|
||||
onSuccess={(data) => {
|
||||
setLogs(data as CliLog[]);
|
||||
}}
|
||||
onClick={async () => {
|
||||
onInvoke={async () => {
|
||||
throw new Error("Not implemented");
|
||||
}}
|
||||
{...props}
|
||||
|
@ -8,16 +8,22 @@ import {
|
||||
Link,
|
||||
} from "@material-ui/core";
|
||||
import { ButtonProps } from "@material-ui/core/Button/Button";
|
||||
import { GetSwapInfoArgs } from "models/tauriModel";
|
||||
import { rpcResetMoneroRecoveryKeys } from "store/features/rpcSlice";
|
||||
import { BobStateName, GetSwapInfoResponseExt } from "models/tauriModelExt";
|
||||
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 DialogHeader from "../../../modal/DialogHeader";
|
||||
import ScrollablePaperTextBox from "../../../other/ScrollablePaperTextBox";
|
||||
|
||||
function MoneroRecoveryKeysDialog() {
|
||||
// TODO: Reimplement this using the new Tauri API
|
||||
return null;
|
||||
|
||||
function MoneroRecoveryKeysDialog({
|
||||
swap_id,
|
||||
...rest
|
||||
}: GetSwapInfoResponseExt) {
|
||||
const dispatch = useAppDispatch();
|
||||
const keys = useAppSelector((s) => s.rpc.state.moneroRecovery);
|
||||
|
||||
@ -25,14 +31,14 @@ function MoneroRecoveryKeysDialog() {
|
||||
dispatch(rpcResetMoneroRecoveryKeys());
|
||||
}
|
||||
|
||||
if (keys === null || keys.swapId !== swap.swap_id) {
|
||||
return <></>;
|
||||
if (keys === null || keys.swapId !== swap_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogHeader
|
||||
title={`Recovery Keys for swap ${swap.swap_id.substring(0, 5)}...`}
|
||||
title={`Recovery Keys for swap ${swap_id.substring(0, 5)}...`}
|
||||
/>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
@ -40,7 +46,7 @@ function MoneroRecoveryKeysDialog() {
|
||||
the multi-signature wallet.
|
||||
<ul>
|
||||
<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>
|
||||
If you have come this far, there is no risk of losing funds. You
|
||||
@ -79,6 +85,7 @@ function MoneroRecoveryKeysDialog() {
|
||||
title={title}
|
||||
copyValue={value}
|
||||
rows={[value]}
|
||||
key={title}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
@ -95,11 +102,8 @@ function MoneroRecoveryKeysDialog() {
|
||||
export function SwapMoneroRecoveryButton({
|
||||
swap,
|
||||
...props
|
||||
}: { swap: GetSwapInfoArgs } & ButtonProps) {
|
||||
return <> </>;
|
||||
/* TODO: Reimplement this using the new Tauri API
|
||||
const isRecoverable = isSwapMoneroRecoverable(swap.state_name);
|
||||
|
||||
}: { swap: GetSwapInfoResponseExt } & ButtonProps) {
|
||||
const isRecoverable = swap.state_name === BobStateName.BtcRedeemed;
|
||||
|
||||
if (!isRecoverable) {
|
||||
return <></>;
|
||||
@ -108,15 +112,15 @@ export function SwapMoneroRecoveryButton({
|
||||
return (
|
||||
<>
|
||||
<PromiseInvokeButton
|
||||
onClick={async () => {
|
||||
throw new Error("Not implemented");
|
||||
}}
|
||||
onInvoke={() => getMoneroRecoveryKeys(swap.swap_id)}
|
||||
onSuccess={(keys) =>
|
||||
store.dispatch(rpcSetMoneroRecoveryKeys([swap.swap_id, keys]))
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
Display Monero Recovery Keys
|
||||
</PromiseInvokeButton>
|
||||
<MoneroRecoveryKeysDialog swap={swap} />
|
||||
<MoneroRecoveryKeysDialog {...swap} />
|
||||
</>
|
||||
);
|
||||
*/
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ export default function WalletRefreshButton() {
|
||||
<PromiseInvokeButton
|
||||
endIcon={<RefreshIcon />}
|
||||
isIconButton
|
||||
onClick={() => checkBitcoinBalance()}
|
||||
onInvoke={() => checkBitcoinBalance()}
|
||||
size="small"
|
||||
/>
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
BuyXmrArgs,
|
||||
BuyXmrResponse,
|
||||
GetSwapInfoResponse,
|
||||
MoneroRecoveryArgs,
|
||||
ResumeSwapArgs,
|
||||
ResumeSwapResponse,
|
||||
SuspendCurrentSwapResponse,
|
||||
@ -23,8 +24,16 @@ import { swapTauriEventReceived } from "store/features/swapSlice";
|
||||
import { store } from "./store/storeRenderer";
|
||||
import { Provider } from "models/apiModel";
|
||||
import { providerToConcatenatedMultiAddr } from "utils/multiAddrUtils";
|
||||
import { MoneroRecoveryResponse } from "models/rpcModel";
|
||||
|
||||
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) => {
|
||||
console.log("Received swap progress event", event.payload);
|
||||
store.dispatch(swapTauriEventReceived(event.payload));
|
||||
@ -99,3 +108,19 @@ export async function resumeSwap(swapId: string) {
|
||||
export async function suspendCurrentSwap() {
|
||||
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() {
|
||||
const swapInfos = useAppSelector((state) => state.rpc.state.swapInfos);
|
||||
|
||||
return sortBy(
|
||||
Object.values(swapInfos),
|
||||
(swap) => -parseDateString(swap.start_date),
|
||||
|
@ -3,8 +3,8 @@ use std::sync::Arc;
|
||||
use swap::cli::{
|
||||
api::{
|
||||
request::{
|
||||
BalanceArgs, BuyXmrArgs, GetHistoryArgs, GetSwapInfosAllArgs, ResumeSwapArgs,
|
||||
SuspendCurrentSwapArgs, WithdrawBtcArgs,
|
||||
BalanceArgs, BuyXmrArgs, GetHistoryArgs, GetSwapInfosAllArgs, MoneroRecoveryArgs,
|
||||
ResumeSwapArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs,
|
||||
},
|
||||
tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle},
|
||||
Context, ContextBuilder,
|
||||
@ -167,7 +167,9 @@ pub fn run() {
|
||||
buy_xmr,
|
||||
resume_swap,
|
||||
get_history,
|
||||
suspend_current_swap
|
||||
monero_recovery,
|
||||
suspend_current_swap,
|
||||
is_context_available,
|
||||
])
|
||||
.setup(setup)
|
||||
.build(tauri::generate_context!())
|
||||
@ -203,6 +205,15 @@ tauri_command!(get_balance, BalanceArgs);
|
||||
tauri_command!(buy_xmr, BuyXmrArgs);
|
||||
tauri_command!(resume_swap, ResumeSwapArgs);
|
||||
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!(get_swap_infos_all, GetSwapInfosAllArgs, 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