feat: wallet seed phrase

---

Co-authored-by: @schowdhuri 
Reviewed-by: @schowdhuri
This commit is contained in:
Mert 2022-05-24 21:26:57 +03:00 committed by GitHub
parent 250d742d48
commit ad493f5147
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 890 additions and 1 deletions

View File

@ -0,0 +1,67 @@
// =============================================================================
// 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 {
createStyles,
Group,
List,
ListItem,
Space,
Stack,
} from "@mantine/core";
import { BodyText } from "@atoms/Typography";
import { useXmrSeed } from "@hooks/haveno/useXmrSeed";
export function SeedPhrase() {
const { classes } = useStyles();
const { data: xmrseeds, isLoading } = useXmrSeed();
const seeds = xmrseeds?.split(" ");
return (
<Stack>
<Space h="lg" />
{isLoading && <BodyText>Loading Wallet Seeds...</BodyText>}
<List type="ordered" className={classes.container}>
{seeds?.map((label, index) => (
<Group key={index} className={classes.background} spacing="sm">
<ListItem>{label}</ListItem>
</Group>
))}
</List>
</Stack>
);
}
const useStyles = createStyles((theme) => ({
background: {
backgroundColor: theme.colors.gray[3],
borderRadius: "0.5rem",
flex: "0 30%",
li: {
color: theme.colors.gray[6],
},
padding: "0.75rem 1rem",
span: {
color: theme.colors.gray[9],
},
width: "15rem",
},
container: {
display: "flex",
flexWrap: "wrap",
gap: "1rem",
},
}));

View File

@ -0,0 +1,30 @@
// =============================================================================
// 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 type { ComponentStory, ComponentMeta } from "@storybook/react";
import { WalletManagement } from ".";
export default {
title: "organisms/Wallet Management",
component: WalletManagement,
} as ComponentMeta<typeof WalletManagement>;
const Template: ComponentStory<typeof WalletManagement> = () => {
return <WalletManagement />;
};
export const Default = Template.bind({});
Default.args = {};

View File

@ -0,0 +1,62 @@
// =============================================================================
// 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 { beforeAll, describe, expect, it, vi } from "vitest";
import { render, waitForElementToBeRemoved } from "@testing-library/react";
import { AppProviders } from "@atoms/AppProviders";
import { WalletManagement } from ".";
import { SeedPhrase } from "./SeedPhrase";
describe("molecules::WalletManagement", () => {
beforeAll(() => {
vi.mock("@hooks/haveno/useValidatePassword", () => ({
useValidatePassword: () => ({
isLoading: false,
isSuccess: true,
data: true,
}),
}));
vi.mock("@hooks/haveno/useXmrSeed", () => ({
useXmrSeed: () => ({
isLoading: true,
isSuccess: true,
data: "baptism ounce solved gimmick cafe absorb pouch gesture fawns degrees bikini inline island oncoming menu tissue cajun inwardly chlorine popular sleepless taboo aces arises popular",
}),
}));
});
it("renders loading state", () => {
const { asFragment } = render(
<AppProviders>
<WalletManagement />
</AppProviders>
);
expect(asFragment()).toMatchSnapshot();
});
it("renders after loading data", async () => {
const { asFragment, queryByText } = render(
<AppProviders>
<SeedPhrase />
</AppProviders>
);
if (queryByText("Loading...")) {
await waitForElementToBeRemoved(() => queryByText("Loading..."));
}
expect(asFragment()).toMatchSnapshot();
});
});

View File

@ -0,0 +1,109 @@
// =============================================================================
// 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 { useState } from "react";
import { FormattedMessage } from "react-intl";
import { useForm } from "@mantine/hooks";
import { Group, Space, Stack } from "@mantine/core";
import { showNotification } from "@mantine/notifications";
import { Button } from "@atoms/Buttons";
import { PasswordInput } from "@atoms/PasswordInput";
import { useValidatePassword } from "@hooks/haveno/useValidatePassword";
import { LangKeys } from "@constants/lang";
import { SeedPhrase } from "./SeedPhrase";
export function WalletManagement() {
const [isRevealed, setRevealed] = useState(false);
const { getInputProps, onSubmit, values, reset } = useForm<FormValues>({
initialValues: {
password: "",
},
});
const { refetch: validatePassword } = useValidatePassword(
{
password: values.password,
},
{
enabled: false,
}
);
const handleSubmit = () => {
validatePassword().then(({ data: isValidPassword }) => {
if (isValidPassword) {
setRevealed(true);
reset();
} else {
setRevealed(false);
showNotification({
color: "red",
title: "Invalid password",
message: "Please check your password",
});
}
});
};
return (
<Stack>
{isRevealed ? (
<Stack>
<SeedPhrase />
<Space h="lg" />
<Group position="left">
<Button
type="submit"
onClick={() => {
setRevealed(false);
}}
>
Hide Seed Phrase
</Button>
</Group>
</Stack>
) : (
<form onSubmit={onSubmit(handleSubmit)}>
<Stack>
<Space h="sm" />
<PasswordInput
autoFocus
id="password"
required
label={
<FormattedMessage
id={LangKeys.AccountWalletPassword}
defaultMessage="Password"
/>
}
{...getInputProps("password")}
/>
<Space h="lg" />
<Group position="right">
<Button type="submit" disabled={values.password ? false : true}>
Reveal Seed Phrase
</Button>
</Group>
</Stack>
</form>
)}
</Stack>
);
}
interface FormValues {
password: string;
}

View File

@ -0,0 +1,494 @@
// Vitest Snapshot v1
exports[`molecules::WalletManagement > renders after loading data 1`] = `
<DocumentFragment>
<div
class="mantine-Stack-root mantine-lfk3cq"
>
<div
class="mantine-63n06h"
/>
<div
class="mantine-Text-root mantine-1ogvxnc"
>
Loading Wallet Seeds...
</div>
<ol
class="mantine-List-root mantine-14df5ct"
>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
baptism
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
ounce
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
solved
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
gimmick
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
cafe
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
absorb
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
pouch
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
gesture
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
fawns
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
degrees
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
bikini
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
inline
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
island
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
oncoming
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
menu
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
tissue
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
cajun
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
inwardly
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
chlorine
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
popular
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
sleepless
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
taboo
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
aces
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
arises
</span>
</div>
</li>
</div>
<div
class="mantine-Group-root mantine-1qkd5ve"
>
<li
class="mantine-List-item mantine-Group-child mantine-xm6zd"
>
<div
class="__mantine-ref-itemWrapper mantine-1hf9p21 mantine-List-itemWrapper"
>
<span>
popular
</span>
</div>
</li>
</div>
</ol>
</div>
</DocumentFragment>
`;
exports[`molecules::WalletManagement > renders loading state 1`] = `
<DocumentFragment>
<div
class="mantine-Stack-root mantine-lfk3cq"
>
<form>
<div
class="mantine-Stack-root mantine-lfk3cq"
>
<div
class="mantine-78ek8g"
/>
<div
class="mantine-PasswordInput-root mantine-14qek68"
>
<label
class="mantine-PasswordInput-label mantine-1bjo575"
for="password"
id="password-label"
>
Password
<span
class="mantine-1wc35tu mantine-PasswordInput-required"
>
*
</span>
</label>
<div
class="mantine-PasswordInput-wrapper mantine-12sbrde"
>
<div
aria-invalid="false"
class="mantine-PasswordInput-defaultVariant mantine-PasswordInput-input mantine-PasswordInput-input mantine-nakpsh"
>
<input
class="mantine-PasswordInput-innerInput mantine-17c0t6q"
id="password"
required=""
type="password"
value=""
/>
</div>
<div
class="mantine-o3oqoy mantine-PasswordInput-rightSection"
>
<button
aria-hidden="true"
class="mantine-ActionIcon-hover mantine-ActionIcon-root mantine-PasswordInput-visibilityToggle mantine-910bvd"
tabindex="-1"
type="button"
>
<svg
fill="none"
height="15"
viewBox="0 0 15 15"
width="15"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M7.5 11C4.80285 11 2.52952 9.62184 1.09622 7.50001C2.52952 5.37816 4.80285 4 7.5 4C10.1971 4 12.4705 5.37816 13.9038 7.50001C12.4705 9.62183 10.1971 11 7.5 11ZM7.5 3C4.30786 3 1.65639 4.70638 0.0760002 7.23501C-0.0253338 7.39715 -0.0253334 7.60288 0.0760014 7.76501C1.65639 10.2936 4.30786 12 7.5 12C10.6921 12 13.3436 10.2936 14.924 7.76501C15.0253 7.60288 15.0253 7.39715 14.924 7.23501C13.3436 4.70638 10.6921 3 7.5 3ZM7.5 9.5C8.60457 9.5 9.5 8.60457 9.5 7.5C9.5 6.39543 8.60457 5.5 7.5 5.5C6.39543 5.5 5.5 6.39543 5.5 7.5C5.5 8.60457 6.39543 9.5 7.5 9.5Z"
fill="currentColor"
fill-rule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
<div
class="mantine-63n06h"
/>
<div
class="mantine-Group-root mantine-147cgkf"
>
<button
class="mantine-Button-filled mantine-Button-root mantine-Group-child mantine-s2bdxi"
disabled=""
type="submit"
>
<div
class="mantine-3xbgk5 mantine-Button-inner"
>
<span
class="mantine-qo1k2 mantine-Button-label"
>
Reveal Seed Phrase
</span>
</div>
</button>
</div>
</div>
</form>
</div>
</DocumentFragment>
`;

View File

@ -0,0 +1,17 @@
// =============================================================================
// 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 * from "./WalletManagement";

View File

@ -50,4 +50,7 @@ export enum LangKeys {
AccountSecurityFieldCurrentPassword = "account.security.field.currentPassword",
AccountSecurityFieldPasswordFormatMsg = "account.security.field.password.format.message",
AccountSecurityFieldRepeatPasswordMatchMsg = "account.security.field.repeatPassword.match.message",
AccountWalletTitle = "account.wallet.title",
AccountWalletDesc = "account.wallet.desc",
AccountWalletPassword = "account.wallet.field.password",
}

View File

@ -58,6 +58,10 @@ const LangPackEN: { [key in LangKeys]: string } = {
[LangKeys.AccountSecurityFieldRepeatPasswordMatchMsg]:
"Passwords don't match",
[LangKeys.CreatePassword]: "Create password",
[LangKeys.AccountWalletTitle]: "Your wallet details",
[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",
};
export default LangPackEN;

View File

@ -59,6 +59,10 @@ const LangPackES: { [key in LangKeys]: string } = {
[LangKeys.AccountSecurityFieldRepeatPasswordMatchMsg]:
"La confirmación de la contraseña no coincide con la contraseña.",
[LangKeys.CreatePassword]: "Crear contraseña",
[LangKeys.AccountWalletTitle]: "Detalles de tu billetera",
[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",
};
export default LangPackES;

View File

@ -26,11 +26,13 @@ export enum QueryKeys {
Prices = "Haveno.Prices",
PrimaryAddress = "Haveno.PrimaryAddress",
SyncStatus = "Haveno.SyncStatus",
XmrSeed = "Haveno.XmrSeed",
// Storage
StorageAccountInfo = "Storage.AccountInfo",
StoragePreferences = "Storage.Preferences",
StorageRemoteMoneroNode = "Storage.RemoteMoneroNode",
StorageIsPasswordValid = "Storage.IsPasswordValid",
// Others
AuthSession = "AuthSession",

View File

@ -0,0 +1,45 @@
// =============================================================================
// 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 { QueryKeys } from "@constants/query-keys";
import { useQuery } from "react-query";
interface Variables {
password: string;
}
interface Options {
enabled: boolean;
}
export function useValidatePassword(variables: Variables, options?: Options) {
return useQuery<boolean>(
QueryKeys.StorageIsPasswordValid,
async () => {
try {
const authToken = await window.electronStore.verifyPassword(
variables.password
);
return Boolean(authToken);
} catch {
return false;
}
},
{
enabled: options?.enabled ?? true,
}
);
}

View File

@ -0,0 +1,26 @@
// =============================================================================
// 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 { useQuery } from "react-query";
import { QueryKeys } from "@constants/query-keys";
import { useHavenoClient } from "./useHavenoClient";
export function useXmrSeed() {
const client = useHavenoClient();
return useQuery(QueryKeys.XmrSeed, async () => {
return client.getXmrSeed();
});
}

View File

@ -14,12 +14,38 @@
// limitations under the License.
// =============================================================================
import { Box, createStyles, Group, Stack } from "@mantine/core";
import { BodyText, Heading } from "@atoms/Typography";
import { WalletManagement } from "@organisms/WalletManagement/WalletManagement";
import { AccountLayout } from "@templates/AccountLayout";
import { LangKeys } from "@constants/lang";
export function Wallet() {
const { classes } = useStyles();
return (
<AccountLayout>
<h1>Account Wallet</h1>
<Box>
<Stack spacing="lg" className={classes.content}>
<Group spacing="sm">
<Heading stringId={LangKeys.AccountWalletTitle} order={3}>
Your wallet details
</Heading>
<BodyText heavy stringId={LangKeys.AccountWalletDesc} size="md">
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.
</BodyText>
</Group>
</Stack>
<WalletManagement />
</Box>
</AccountLayout>
);
}
const useStyles = createStyles((theme) => ({
content: {
maxWidth: theme.other.contentWidthMd,
},
}));