mirror of
https://github.com/haveno-dex/haveno-ui.git
synced 2024-12-24 23:19:41 -05:00
feat: account backup
--- Co-authored-by: @schowdhuri Reviewed-by: @schowdhuri
This commit is contained in:
parent
ad493f5147
commit
500be9a7fd
@ -18,6 +18,7 @@ import { app } from "electron";
|
||||
import "./security-restrictions";
|
||||
import { restoreOrCreateWindow } from "@src/mainWindow";
|
||||
import { registerStoreHandlers } from "@src/services/store";
|
||||
import { registerHavenoHandlers } from "./services/haveno";
|
||||
|
||||
/**
|
||||
* Prevent multiple instances
|
||||
@ -61,6 +62,11 @@ app
|
||||
.then(registerStoreHandlers)
|
||||
.catch((e) => console.error("Failed to register store handlers:", e));
|
||||
|
||||
app
|
||||
.whenReady()
|
||||
.then(registerHavenoHandlers)
|
||||
.catch((e) => console.error("Failed to register haveno handlers:", e));
|
||||
|
||||
/**
|
||||
* Install devtools in development mode only
|
||||
*/
|
||||
|
59
packages/main/src/services/haveno.ts
Normal file
59
packages/main/src/services/haveno.ts
Normal file
@ -0,0 +1,59 @@
|
||||
// =============================================================================
|
||||
// Copyright 2022 Haveno
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
import fsPromises from "fs/promises";
|
||||
import { ipcMain, dialog } from "electron";
|
||||
import type { DownloadBackupInput } from "@src/types";
|
||||
import { IpcChannels } from "@src/types";
|
||||
|
||||
export function registerHavenoHandlers() {
|
||||
ipcMain.handle(
|
||||
IpcChannels.DownloadBackup,
|
||||
async (_, data: DownloadBackupInput) => {
|
||||
const file = await dialog.showSaveDialog({
|
||||
defaultPath: "haveno-backup",
|
||||
filters: [
|
||||
{
|
||||
extensions: ["zip"],
|
||||
name: "*",
|
||||
},
|
||||
],
|
||||
properties: ["createDirectory", "dontAddToRecent"],
|
||||
});
|
||||
if (!file?.filePath) {
|
||||
return 0;
|
||||
}
|
||||
await fsPromises.writeFile(file.filePath, new Uint8Array(data.bytes));
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle(IpcChannels.RestoreBackup, async (): Promise<Uint8Array> => {
|
||||
const files = await dialog.showOpenDialog({
|
||||
filters: [
|
||||
{
|
||||
extensions: ["zip"],
|
||||
name: "*",
|
||||
},
|
||||
],
|
||||
properties: ["openFile", "dontAddToRecent"],
|
||||
});
|
||||
if (!files?.filePaths[0]) {
|
||||
return new Uint8Array();
|
||||
}
|
||||
const zipFile = files.filePaths[0];
|
||||
return new Uint8Array(await fsPromises.readFile(zipFile));
|
||||
});
|
||||
}
|
@ -14,12 +14,6 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
import { AccountLayout } from "@templates/AccountLayout";
|
||||
|
||||
export function Backup() {
|
||||
return (
|
||||
<AccountLayout>
|
||||
<h1>Account Backup</h1>
|
||||
</AccountLayout>
|
||||
);
|
||||
export interface DownloadBackupInput {
|
||||
bytes: ArrayBuffer;
|
||||
}
|
@ -16,3 +16,4 @@
|
||||
|
||||
export * from "./ipc";
|
||||
export * from "./store";
|
||||
export * from "./haveno";
|
||||
|
@ -15,6 +15,7 @@
|
||||
// =============================================================================
|
||||
|
||||
export enum IpcChannels {
|
||||
// store
|
||||
GetAccountInfo = "store:accountInfo",
|
||||
SetPassword = "store:accountinfo.setPassword",
|
||||
ChangePassword = "store:accountinfo.changePassword",
|
||||
@ -23,5 +24,10 @@ export enum IpcChannels {
|
||||
GetPreferences = "store:preferences",
|
||||
SetMoneroNode = "store:preferences.setMoneroNode",
|
||||
|
||||
// haveno
|
||||
DownloadBackup = "haveno:downloadBackup",
|
||||
RestoreBackup = "haveno:restoreBackup",
|
||||
|
||||
// others
|
||||
VerifyAuthToken = "verifyAuthToken",
|
||||
}
|
||||
|
1
packages/preload/contracts.d.ts
vendored
1
packages/preload/contracts.d.ts
vendored
@ -20,6 +20,7 @@ interface Exposed {
|
||||
readonly nodeCrypto: Readonly<typeof import("./src/nodeCrypto").nodeCrypto>;
|
||||
readonly versions: Readonly<typeof import("./src/versions").versions>;
|
||||
readonly electronStore: Readonly<typeof import("./src/store").store>;
|
||||
readonly haveno: Readonly<typeof import("./src/haveno").store>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
|
31
packages/preload/src/haveno.ts
Normal file
31
packages/preload/src/haveno.ts
Normal file
@ -0,0 +1,31 @@
|
||||
// =============================================================================
|
||||
// Copyright 2022 Haveno
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
import { ipcRenderer } from "electron";
|
||||
import { exposeInMainWorld } from "./exposeInMainWorld";
|
||||
import type { DownloadBackupInput } from "./types";
|
||||
import { IpcChannels } from "./types";
|
||||
|
||||
// Export for types in contracts.d.ts
|
||||
export const haveno = {
|
||||
downloadBackup: async (data: DownloadBackupInput): Promise<void> =>
|
||||
ipcRenderer.invoke(IpcChannels.DownloadBackup, data),
|
||||
|
||||
getBackupData: async (): Promise<Uint8Array> =>
|
||||
ipcRenderer.invoke(IpcChannels.RestoreBackup),
|
||||
};
|
||||
|
||||
exposeInMainWorld("haveno", haveno);
|
@ -21,3 +21,4 @@
|
||||
import "./nodeCrypto";
|
||||
import "./versions";
|
||||
import "./store";
|
||||
import "./haveno";
|
||||
|
@ -21,7 +21,7 @@ import { Home } from "@pages/Home";
|
||||
import { Login } from "@pages/Login";
|
||||
import { CreateAccount, Welcome } from "@pages/Onboarding";
|
||||
import {
|
||||
Backup,
|
||||
AccountBackup,
|
||||
Settings,
|
||||
PaymentAccounts,
|
||||
Security,
|
||||
@ -57,7 +57,7 @@ export function AppRoutes() {
|
||||
path={ROUTES.Backup}
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Backup />
|
||||
<AccountBackup />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
@ -53,4 +53,12 @@ export enum LangKeys {
|
||||
AccountWalletTitle = "account.wallet.title",
|
||||
AccountWalletDesc = "account.wallet.desc",
|
||||
AccountWalletPassword = "account.wallet.field.password",
|
||||
AccountBackupDownloadTitle = "account.backup.download.title",
|
||||
AccountBackupDownloadDesc = "account.backup.download.desc",
|
||||
AccountBackupDownloadBtn = "account.backup.download.btn",
|
||||
AccountBackupRestoreTitle = "account.backup.restore.title",
|
||||
AccountBackupRestoreDesc = "account.backup.restore.desc",
|
||||
AccountBackupRestoreBtn = "account.backup.restore.btn",
|
||||
AccountBackupDownloadSuccessNotif = "account.backup.download.successNotification",
|
||||
AccountBackupRestoreSuccessNotif = "account.backup.restore.successNotification",
|
||||
}
|
||||
|
@ -62,6 +62,18 @@ const LangPackEN: { [key in LangKeys]: string } = {
|
||||
[LangKeys.AccountWalletDesc]:
|
||||
"The Haveno wallet is permanently connected to your account. Solely saving your seed phrase is not enough to recover your account, you need to download a backup of your account, which you can download via the backup section.",
|
||||
[LangKeys.AccountWalletPassword]: "Password",
|
||||
[LangKeys.AccountBackupDownloadTitle]: "Download your backup file",
|
||||
[LangKeys.AccountBackupDownloadDesc]:
|
||||
"To be able to restore your Haveno account you need to create a backup file of your account. Keep it somewhere safe.",
|
||||
[LangKeys.AccountBackupDownloadBtn]: "Download backup file",
|
||||
[LangKeys.AccountBackupRestoreTitle]: "Restore an existing backup file",
|
||||
[LangKeys.AccountBackupRestoreDesc]:
|
||||
"When you restore an existing backup file of your Haveno account, you will lose the account you’re using currently. Please use with caution.",
|
||||
[LangKeys.AccountBackupRestoreBtn]: "Restore backup",
|
||||
[LangKeys.AccountBackupDownloadSuccessNotif]:
|
||||
"The backup has been downloaded successfully.",
|
||||
[LangKeys.AccountBackupRestoreSuccessNotif]:
|
||||
"The backup has been restored successfully.",
|
||||
};
|
||||
|
||||
export default LangPackEN;
|
||||
|
@ -63,6 +63,21 @@ const LangPackES: { [key in LangKeys]: string } = {
|
||||
[LangKeys.AccountWalletDesc]:
|
||||
"La billetera Haveno está permanentemente conectada a su cuenta. Solo guardar su frase inicial no es suficiente para recuperar su cuenta, necesita descargar una copia de seguridad de su cuenta, que puede descargar a través de la sección de copia de seguridad.",
|
||||
[LangKeys.AccountWalletPassword]: "contraseña",
|
||||
[LangKeys.AccountBackupDownloadTitle]:
|
||||
"Descarga tu archivo de copia de seguridad",
|
||||
[LangKeys.AccountBackupDownloadDesc]:
|
||||
"Para poder restore your Haveno account you need to create a backup file of your account. Keep it somewhere safe.",
|
||||
[LangKeys.AccountBackupDownloadBtn]:
|
||||
"Descargar archivo de copia de seguridad",
|
||||
[LangKeys.AccountBackupRestoreTitle]:
|
||||
"Restaurar un archivo de copia de seguridad existente",
|
||||
[LangKeys.AccountBackupRestoreDesc]:
|
||||
"Cuando restaure un archivo de respaldo existente de su cuenta de Haveno, perderá la cuenta que está usando actualmente. Úselo con precaución.",
|
||||
[LangKeys.AccountBackupRestoreBtn]: "Restaurar copia de seguridad",
|
||||
[LangKeys.AccountBackupDownloadSuccessNotif]:
|
||||
"La copia de seguridad se ha descargado correctamente.",
|
||||
[LangKeys.AccountBackupRestoreSuccessNotif]:
|
||||
"La copia de seguridad se ha restaurado correctamente.",
|
||||
};
|
||||
|
||||
export default LangPackES;
|
||||
|
20
packages/renderer/src/constants/notifications.ts
Normal file
20
packages/renderer/src/constants/notifications.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// =============================================================================
|
||||
// Copyright 2022 Haveno
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
export enum Notifications {
|
||||
AccountRestoring = "AccountRestoring",
|
||||
MoneroRestartAfterRestoring = "MoneroRestartAfterRestoring",
|
||||
}
|
61
packages/renderer/src/hooks/haveno/useDownloadBackup.ts
Normal file
61
packages/renderer/src/hooks/haveno/useDownloadBackup.ts
Normal file
@ -0,0 +1,61 @@
|
||||
// =============================================================================
|
||||
// Copyright 2022 Haveno
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
import { useMutation } from "react-query";
|
||||
import type { HavenoClient } from "haveno-ts";
|
||||
import { useHavenoClient } from "./useHavenoClient";
|
||||
|
||||
export function useDownloadBackup() {
|
||||
const client = useHavenoClient();
|
||||
return useMutation(async () => {
|
||||
const bytes = await getBackupData(client);
|
||||
return window.haveno.downloadBackup({
|
||||
bytes,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function getBackupData(client: HavenoClient) {
|
||||
const SIZE_INCREMENT = 4096;
|
||||
let size = SIZE_INCREMENT;
|
||||
let result = new ArrayBuffer(size);
|
||||
let numBytes = 0;
|
||||
|
||||
const writableStream = new WritableStream({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
write(chunk: Array<any>) {
|
||||
const len = chunk.length;
|
||||
if (numBytes + len > size) {
|
||||
while (numBytes + len > size) {
|
||||
size += SIZE_INCREMENT;
|
||||
}
|
||||
const largerBuffer = new ArrayBuffer(size);
|
||||
new Uint8Array(largerBuffer).set(new Uint8Array(result));
|
||||
result = largerBuffer;
|
||||
}
|
||||
const view = new Uint8Array(result);
|
||||
chunk.forEach((byte, index) => {
|
||||
view[numBytes + index] = byte;
|
||||
});
|
||||
numBytes += len;
|
||||
},
|
||||
abort(err) {
|
||||
console.log("Sink error:", err);
|
||||
},
|
||||
});
|
||||
await client.backupAccount(writableStream.getWriter());
|
||||
return result.slice(0, numBytes);
|
||||
}
|
82
packages/renderer/src/hooks/haveno/useRestoreBackup.ts
Normal file
82
packages/renderer/src/hooks/haveno/useRestoreBackup.ts
Normal file
@ -0,0 +1,82 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
// =============================================================================
|
||||
// Copyright 2022 Haveno
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
import { hideNotification, showNotification } from "@mantine/notifications";
|
||||
import { useMutation } from "react-query";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { MoneroNodeSettings } from "haveno-ts";
|
||||
import { useHavenoClient } from "./useHavenoClient";
|
||||
import { Notifications } from "@constants/notifications";
|
||||
import { deleteSession } from "@utils/session";
|
||||
import { ROUTES } from "@constants/routes";
|
||||
|
||||
export function useRestoreBackup() {
|
||||
const client = useHavenoClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return useMutation(
|
||||
async () => {
|
||||
const bytes = await window.haveno.getBackupData();
|
||||
|
||||
if (!bytes.length) {
|
||||
return;
|
||||
}
|
||||
showNotification({
|
||||
id: Notifications.AccountRestoring,
|
||||
title: "Account Restoring.",
|
||||
message: "Account is restoring from the file.",
|
||||
loading: true,
|
||||
});
|
||||
await client.deleteAccount();
|
||||
await client.restoreAccount(bytes);
|
||||
|
||||
hideNotification(Notifications.AccountRestoring);
|
||||
showNotification({
|
||||
id: Notifications.MoneroRestartAfterRestoring,
|
||||
title: "Monero restarting.",
|
||||
message:
|
||||
"The account has been restored, now the Monero node restarting.",
|
||||
loading: true,
|
||||
});
|
||||
deleteSession();
|
||||
navigate(ROUTES.Login);
|
||||
|
||||
if (await client.isMoneroNodeRunning()) {
|
||||
await client.stopMoneroNode();
|
||||
}
|
||||
try {
|
||||
await client.startMoneroNode(new MoneroNodeSettings());
|
||||
} catch (ex) {
|
||||
console.log(ex);
|
||||
throw new Error("Failed to start the monero node");
|
||||
}
|
||||
hideNotification(Notifications.MoneroRestartAfterRestoring);
|
||||
},
|
||||
{
|
||||
onError: (err: Error) => {
|
||||
hideNotification(Notifications.AccountRestoring);
|
||||
hideNotification(Notifications.MoneroRestartAfterRestoring);
|
||||
|
||||
showNotification({
|
||||
color: "red",
|
||||
message: err?.message ?? "Unable to restore backup",
|
||||
title: "Something went wrong",
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
135
packages/renderer/src/pages/Account/AccountBackup.tsx
Normal file
135
packages/renderer/src/pages/Account/AccountBackup.tsx
Normal file
@ -0,0 +1,135 @@
|
||||
// =============================================================================
|
||||
// Copyright 2022 Haveno
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { showNotification } from "@mantine/notifications";
|
||||
import { Box, createStyles, Stack } from "@mantine/core";
|
||||
import { Button } from "@atoms/Buttons";
|
||||
import { BodyText, Heading } from "@atoms/Typography";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { useDownloadBackup } from "@hooks/haveno/useDownloadBackup";
|
||||
import { useRestoreBackup } from "@hooks/haveno/useRestoreBackup";
|
||||
import { AccountLayout } from "@templates/AccountLayout";
|
||||
|
||||
export function AccountBackup() {
|
||||
const { classes } = useStyles();
|
||||
const intl = useIntl();
|
||||
|
||||
const { mutateAsync: downloadBackup, isLoading: isDownloading } =
|
||||
useDownloadBackup();
|
||||
const { mutateAsync: restoreBackup, isLoading: isRestoring } =
|
||||
useRestoreBackup();
|
||||
|
||||
const handleDownloadBtnClick = () => {
|
||||
downloadBackup().then(() => {
|
||||
showNotification({
|
||||
color: "green",
|
||||
message: intl.formatMessage({
|
||||
id: LangKeys.AccountBackupDownloadSuccessNotif,
|
||||
defaultMessage: "The backup downloaded successfully.",
|
||||
}),
|
||||
});
|
||||
});
|
||||
};
|
||||
const handleRestoreBtnClick = () => {
|
||||
restoreBackup().then(() => {
|
||||
showNotification({
|
||||
color: "green",
|
||||
message: intl.formatMessage({
|
||||
id: LangKeys.AccountBackupRestoreSuccessNotif,
|
||||
defaultMessage: "The backup restored successfully.",
|
||||
}),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AccountLayout>
|
||||
<Stack spacing={40}>
|
||||
<Box>
|
||||
<Heading
|
||||
className={classes.title}
|
||||
order={3}
|
||||
stringId={LangKeys.AccountBackupDownloadTitle}
|
||||
>
|
||||
Download your backup file
|
||||
</Heading>
|
||||
|
||||
<BodyText
|
||||
stringId={LangKeys.AccountBackupDownloadDesc}
|
||||
className={classes.desc}
|
||||
>
|
||||
To be able to restore your Haveno account you need to create a
|
||||
backup file of your account. Keep it somewhere safe.
|
||||
</BodyText>
|
||||
|
||||
<Button
|
||||
disabled={isRestoring}
|
||||
loading={isDownloading}
|
||||
loaderPosition="right"
|
||||
onClick={handleDownloadBtnClick}
|
||||
>
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountBackupDownloadBtn}
|
||||
defaultMessage="Download backup file"
|
||||
/>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Heading
|
||||
order={3}
|
||||
stringId={LangKeys.AccountBackupRestoreTitle}
|
||||
className={classes.title}
|
||||
>
|
||||
Restore an existing backup file
|
||||
</Heading>
|
||||
|
||||
<BodyText
|
||||
stringId={LangKeys.AccountBackupRestoreDesc}
|
||||
className={classes.desc}
|
||||
>
|
||||
When you restore an existing backup file of your Haveno account, you
|
||||
will lose the account you’re using currently. Please use with
|
||||
caution.
|
||||
</BodyText>
|
||||
|
||||
<Button
|
||||
disabled={true}
|
||||
loading={isRestoring}
|
||||
loaderPosition="right"
|
||||
flavor="neutral"
|
||||
onClick={handleRestoreBtnClick}
|
||||
>
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountBackupRestoreBtn}
|
||||
defaultMessage="Restore backup"
|
||||
/>
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</AccountLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
title: {
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
desc: {
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
}));
|
@ -16,8 +16,8 @@
|
||||
|
||||
export * from "./AddPaymentAccount";
|
||||
export * from "./PaymentMethods";
|
||||
export * from "./Backup";
|
||||
export * from "./Settings";
|
||||
export * from "./PaymentAccounts";
|
||||
export * from "./Security";
|
||||
export * from "./Wallet";
|
||||
export * from "./AccountBackup";
|
||||
|
Loading…
Reference in New Issue
Block a user