Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update Keychain for new modified-zip32 #1624

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
43 changes: 26 additions & 17 deletions apps/extension/src/App/Accounts/ParentAccounts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import { PageHeader } from "App/Common";
import routes from "App/routes";
import { ParentAccount } from "background/keyring";
import { AccountContext } from "context";
import { openSetupTab } from "utils";
import { isOutdatedShieldedAccount, openSetupTab } from "utils";

type Account = DerivedAccount & { outdated: boolean };
/**
* Represents the extension's settings page.
*/
Expand All @@ -27,25 +28,33 @@ export const ParentAccounts = (): JSX.Element => {
changeActiveAccountId,
} = useContext(AccountContext);

const allParentAccounts = parentAccounts.reduce(
(acc, account) => {
acc[account.id] = { ...account, outdated: false };
return acc;
},
{} as Record<string, Account>
);

// We check which accounts need to be re-imported
const accounts = allAccounts
allAccounts
.filter(
(account) => account.parentId || account.type === AccountType.Ledger
)
.map((account) => {
const outdated =
account.type !== AccountType.Ledger &&
typeof account.pseudoExtendedKey === "undefined";

// The only account without a parent is the ledger account
const parent =
parentAccounts.find((pa) => pa.id === account.parentId) || account;

return { ...parent, outdated };
.forEach((account) => {
const parent = allParentAccounts[account.parentId!];
if (parent) {
const outdated = isOutdatedShieldedAccount(account, parent.type);
allParentAccounts[parent.id] = { ...parent, outdated };
}
});

const accounts = Object.values(allParentAccounts);

useEffect(() => {
const hasOutdatedAccounts = accounts.some((account) => account.outdated);
const hasOutdatedAccounts = accounts.some(
(account: Account) => account.outdated
);
// If there are outdated accounts, we redirect to the update required page
if (hasOutdatedAccounts) {
navigate(routes.accountsUpdateRequired());
Expand All @@ -56,19 +65,19 @@ export const ParentAccounts = (): JSX.Element => {
await openSetupTab();
};

const goToViewAccount = (account: DerivedAccount): void => {
const goToViewAccount = (account: Account): void => {
navigate(routes.viewAccount(account.id));
};

const goToDeletePage = (account: DerivedAccount): void => {
const goToDeletePage = (account: Account): void => {
navigate(routes.deleteAccount(account.id), { state: { account } });
};

const goToViewRecoveryPhrase = (account: DerivedAccount): void => {
const goToViewRecoveryPhrase = (account: Account): void => {
navigate(routes.viewAccountMnemonic(account.id));
};

const goToRenameAccount = (account: DerivedAccount): void => {
const goToRenameAccount = (account: Account): void => {
navigate(routes.renameAccount(account.id), { state: { account } });
};

Expand Down
25 changes: 18 additions & 7 deletions apps/extension/src/Setup/Common/AdvancedOptionsMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Alert, LinkButton, Stack } from "@namada/components";
import { Bip44Path } from "@namada/types";
import { Bip44Path, Zip32Path } from "@namada/types";
import clsx from "clsx";
import React, { useState } from "react";
import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
import Bip39PassphraseForm from "./Bip39PassphraseForm";
import Bip44Form from "./Bip44Form";
import CustomPathForm from "./CustomPathForm";

type Props = {
passphrase: string;
path: Bip44Path;
bip44Path: Bip44Path;
zip32Path: Zip32Path;
setPassphrase: (passphrase: string) => void;
setPath: (path: Bip44Path) => void;
setBip44Path: (path: Bip44Path) => void;
setZip32Path: (path: Zip32Path) => void;
};

export enum Option {
Expand All @@ -21,9 +23,11 @@ export enum Option {

export const AdvancedOptionsMenu: React.FC<Props> = ({
passphrase,
path,
bip44Path,
zip32Path,
setPassphrase,
setPath,
setBip44Path,
setZip32Path,
}) => {
const [option, setOption] = useState(Option.Menu);
const menuText: Record<Option, string> = {
Expand Down Expand Up @@ -84,7 +88,14 @@ export const AdvancedOptionsMenu: React.FC<Props> = ({
</div>
</div>
{option === Option.Path && (
<Bip44Form path={path} setPath={setPath} />
<>
<CustomPathForm
bip44Path={bip44Path}
zip32Path={zip32Path}
setBip44Path={setBip44Path}
setZip32Path={setZip32Path}
/>
</>
)}
{option === Option.Passphrase && (
<Bip39PassphraseForm
Expand Down
106 changes: 37 additions & 69 deletions apps/extension/src/Setup/Common/Bip44Form.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";

import { chains } from "@namada/chains";
import { Alert, Input, LinkButton, Stack } from "@namada/components";
import { Input } from "@namada/components";
import { Bip44Path } from "@namada/types";

type Props = {
Expand All @@ -28,77 +28,45 @@ const Bip44Form: React.FC<Props> = ({ path, setPath }) => {

const parentDerivationPath = `m/44'/${coinType}'/`;

const handleReset = (): void => {
setPath({ account: 0, change: 0, index: 0 });
};

return (
<div className="flex flex-col w-full">
<div className="my-3">
<Alert
type="info"
title="Please note"
className="[&_strong]:normal-case"
>
<Stack gap={6}>
<ul className="text-white list-disc mx-6 [&_button]:text-white">
<li>
You can create multiple addresses from one recovery phrase
</li>
<li>A lost path cannot be recovered</li>
<li>
If you&apos;re unfamiliar with this feature, skip or undo this
step -{" "}
<LinkButton
className="underline bold"
role="button"
onClick={(e) => {
e.preventDefault();
handleReset();
}}
>
Reset
</LinkButton>
</li>
</ul>
<label className="text-base font-medium text-neutral-300">
HD Derivation Path
<div className="flex w-full justify-start items-center pt-2">
<span className="h-px px-1 text-xs text-neutral-300">
{parentDerivationPath}
</span>
<Input
type="number"
className="w-60"
min="0"
value={path.account}
onChange={(e) => handleNumericChange(e, "account")}
onFocus={handleFocus}
/>
<i>&apos;/</i>
<Input
type="number"
max="1"
className="w-60"
min="0"
value={path.change}
onChange={(e) => handleNumericChange(e, "change")}
onFocus={handleFocus}
/>
<i>&apos;/</i>
<Input
type="number"
className="w-60"
min="0"
value={path.index}
onChange={(e) => handleNumericChange(e, "index")}
onFocus={handleFocus}
/>
<i>&apos;</i>
</div>
</label>
</Stack>
</Alert>
<label className="text-base font-medium text-neutral-300">
Transparent Path
<div className="flex w-full justify-start items-center pt-2">
<span className="h-px px-1 text-xs text-neutral-300">
{parentDerivationPath}
</span>
<Input
type="number"
className="w-60"
min="0"
value={path.account}
onChange={(e) => handleNumericChange(e, "account")}
onFocus={handleFocus}
/>
<i>&apos;/</i>
<Input
type="number"
className="w-60"
min="0"
max="1"
value={path.change}
onChange={(e) => handleNumericChange(e, "change")}
onFocus={handleFocus}
/>
<i>&apos;/</i>
<Input
type="number"
className="w-60"
min="0"
value={path.index}
onChange={(e) => handleNumericChange(e, "index")}
onFocus={handleFocus}
/>
<i>&apos;</i>
</div>
</label>
</div>
</div>
);
Expand Down
64 changes: 64 additions & 0 deletions apps/extension/src/Setup/Common/CustomPathForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from "react";

import { Alert, LinkButton, Stack } from "@namada/components";
import { Bip44Path, Zip32Path } from "@namada/types";
import Bip44Form from "./Bip44Form";
import Zip32Form from "./Zip32Form";

type Props = {
bip44Path: Bip44Path;
zip32Path: Zip32Path;
setBip44Path: (path: Bip44Path) => void;
setZip32Path: (path: Zip32Path) => void;
};

const CustomPathForm: React.FC<Props> = ({
bip44Path,
zip32Path,
setBip44Path,
setZip32Path,
}) => {
const handleReset = (): void => {
setBip44Path({ account: 0, change: 0, index: 0 });
setZip32Path({ account: 0 });
};

return (
<div className="flex flex-col w-full">
<div className="my-3">
<Alert
type="info"
title="Please note"
className="[&_strong]:normal-case"
>
<Stack gap={6}>
<ul className="text-white list-disc mx-6 [&_button]:text-white">
<li>
You can create multiple addresses from one recovery phrase
</li>
<li>A lost path cannot be recovered</li>
<li>
If you&apos;re unfamiliar with this feature, skip or undo this
step -{" "}
<LinkButton
className="underline bold"
role="button"
onClick={(e) => {
e.preventDefault();
handleReset();
}}
>
Reset
</LinkButton>
</li>
</ul>
<Bip44Form path={bip44Path} setPath={setBip44Path} />
<Zip32Form path={zip32Path} setPath={setZip32Path} />
</Stack>
</Alert>
</div>
</div>
);
};

export default CustomPathForm;
50 changes: 50 additions & 0 deletions apps/extension/src/Setup/Common/Zip32Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from "react";

import { chains } from "@namada/chains";
import { Input } from "@namada/components";
import { Zip32Path } from "@namada/types";

type Props = {
path: Zip32Path;
setPath: (path: Zip32Path) => void;
};

const Zip32Form: React.FC<Props> = ({ path, setPath }) => {
const { coinType } = chains.namada.bip44;

const handleNumericChange = (
e: React.ChangeEvent<HTMLInputElement>
): void => {
const result = e.target.value.replace(/\D/g, "") || "0";
setPath({
account: parseInt(result),
});
};

const handleFocus = (e: React.ChangeEvent<HTMLInputElement>): void =>
e.target.select();

const parentDerivationPath = `m/32'/${coinType}'/`;

return (
<label className="text-base font-medium text-neutral-300">
Shielded Path
<div className="flex w-full justify-start items-center pt-2">
<span className="h-px px-1 text-xs text-neutral-300">
{parentDerivationPath}
</span>
<Input
type="number"
className="w-60"
min="0"
value={path.account}
onChange={handleNumericChange}
onFocus={handleFocus}
/>
<i>&apos;</i>
</div>
</label>
);
};

export default Zip32Form;
Loading