Skip to content

Commit

Permalink
feat: begin implementing zip32 form, fix account persistence
Browse files Browse the repository at this point in the history
  • Loading branch information
jurevans committed Feb 12, 2025
1 parent 75b2cbc commit 8750cfe
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 33 deletions.
2 changes: 2 additions & 0 deletions apps/extension/src/App/Accounts/ParentAccounts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const ParentAccounts = (): JSX.Element => {
changeActiveAccountId,
} = useContext(AccountContext);

console.log({ allAccounts });

// We check which accounts need to be re-imported
const accounts = allAccounts
.filter(
Expand Down
20 changes: 14 additions & 6 deletions apps/extension/src/Setup/Common/AdvancedOptionsMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
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 Zip32Form from "./Zip32Form";

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 +24,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 +89,10 @@ export const AdvancedOptionsMenu: React.FC<Props> = ({
</div>
</div>
{option === Option.Path && (
<Bip44Form path={path} setPath={setPath} />
<>
<Bip44Form path={bip44Path} setPath={setBip44Path} />
<Zip32Form path={zip32Path} setPath={setZip32Path} />
</>
)}
{option === Option.Passphrase && (
<Bip39PassphraseForm
Expand Down
86 changes: 86 additions & 0 deletions apps/extension/src/Setup/Common/Zip32Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React from "react";

import { chains } from "@namada/chains";
import { Alert, Input, LinkButton, Stack } 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}'/`;

const handleReset = (): void => {
setPath({ 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>
<label className="text-base font-medium text-neutral-300">
ZIP32 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>
</Stack>
</Alert>
</div>
</div>
);
};

export default Zip32Form;
20 changes: 13 additions & 7 deletions apps/extension/src/Setup/ImportAccount/SeedPhraseImport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
RadioGroup,
Stack,
} from "@namada/components";
import { Bip44Path } from "@namada/types";
import { Bip44Path, Zip32Path } from "@namada/types";
import { assertNever } from "@namada/utils";
import { AccountSecret, ValidateMnemonicMsg } from "background/keyring";
import { useRequester } from "hooks/useRequester";
Expand All @@ -19,8 +19,10 @@ import { fillArray, filterPrivateKeyPrefix, validatePrivateKey } from "utils";

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

const SHORT_PHRASE_COUNT = 12;
Expand All @@ -34,8 +36,10 @@ enum SecretType {

export const SeedPhraseImport: React.FC<Props> = ({
onConfirm,
path,
setPath,
bip44Path,
zip32Path,
setBip44Path,
setZip32Path,
}) => {
const requester = useRequester();
const [privateKey, setPrivateKey] = useState("");
Expand Down Expand Up @@ -241,8 +245,10 @@ export const SeedPhraseImport: React.FC<Props> = ({
{mnemonicType !== SecretType.PrivateKey && (
<AdvancedOptions>
<AdvancedOptionsMenu
path={path}
setPath={setPath}
bip44Path={bip44Path}
zip32Path={zip32Path}
setBip44Path={setBip44Path}
setZip32Path={setZip32Path}
passphrase={bip39Passphrase}
setPassphrase={setBip39Passphrase}
/>
Expand Down
22 changes: 16 additions & 6 deletions apps/extension/src/Setup/Ledger/LedgerConnect.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { chains } from "@namada/chains";
import { ActionButton, Alert, Image, Stack } from "@namada/components";
import { Ledger as LedgerApp, makeBip44Path } from "@namada/sdk/web";
import { Bip44Path } from "@namada/types";
import { Bip44Path, Zip32Path } from "@namada/types";
import { LedgerError } from "@zondax/ledger-namada";
import { LedgerStep } from "Setup/Common";
import { AdvancedOptions } from "Setup/Common/AdvancedOptions";
Expand All @@ -11,11 +11,18 @@ import React, { useState } from "react";
import { useNavigate } from "react-router-dom";

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

export const LedgerConnect: React.FC<Props> = ({ path, setPath }) => {
export const LedgerConnect: React.FC<Props> = ({
bip44Path,
zip32Path,
setBip44Path,
setZip32Path,
}) => {
const navigate = useNavigate();
const [error, setError] = useState<string>();
const [isLedgerConnecting, setIsLedgerConnecting] = useState(false);
Expand All @@ -34,8 +41,9 @@ export const LedgerConnect: React.FC<Props> = ({ path, setPath }) => {

setIsLedgerConnecting(true);
const { address, publicKey } = await ledger.showAddressAndPublicKey(
makeBip44Path(chains.namada.bip44.coinType, path)
makeBip44Path(chains.namada.bip44.coinType, bip44Path)
);
// TODO: Shielded import will be enabled in PR: https://github.com/anoma/namada-interface/pull/1575
setIsLedgerConnecting(false);
navigate(routes.ledgerImport(), {
state: {
Expand Down Expand Up @@ -99,7 +107,9 @@ export const LedgerConnect: React.FC<Props> = ({ path, setPath }) => {
)}

<AdvancedOptions>
<Bip44Form path={path} setPath={setPath} />
<Bip44Form path={bip44Path} setPath={setBip44Path} />
{/* TODO: Enable in https://github.com/anoma/namada-interface/pull/1575 */}
{/*<Zip32Form path={zip32Path} setPath={setZip32Path} />*/}
</AdvancedOptions>

<LedgerStep
Expand Down
11 changes: 7 additions & 4 deletions apps/extension/src/Setup/Ledger/LedgerImport.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ActionButton, Alert, Loading, Stack } from "@namada/components";
import { Bip44Path } from "@namada/types";
import { Bip44Path, Zip32Path } from "@namada/types";
import { AccountAlias, Password } from "Setup/Common";
import { AccountManager } from "Setup/query";
import routes from "Setup/routes";
Expand All @@ -14,11 +14,13 @@ type LedgerImportLocationState = {

type LedgerProps = {
passwordRequired: boolean;
path: Bip44Path;
bip44Path: Bip44Path;
zip32Path: Zip32Path;
};

export const LedgerImport = ({
path,
bip44Path,
zip32Path: _,
passwordRequired,
}: LedgerProps): JSX.Element => {
const location = useLocation();
Expand Down Expand Up @@ -60,7 +62,8 @@ export const LedgerImport = ({
alias,
address,
publicKey,
path,
path: bip44Path,
// TODO: Enable shielded
});

navigate(routes.ledgerComplete(), {
Expand Down
33 changes: 23 additions & 10 deletions apps/extension/src/Setup/Setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Container,
LifecycleExecutionWrapper as Wrapper,
} from "@namada/components";
import { Bip44Path, DerivedAccount } from "@namada/types";
import { Bip44Path, DerivedAccount, Zip32Path } from "@namada/types";
import { assertNever } from "@namada/utils";
import { AccountSecret, AccountStore } from "background/keyring";
import { AnimatePresence, motion } from "framer-motion";
Expand Down Expand Up @@ -74,11 +74,14 @@ export const Setup: React.FC = () => {
const [currentStep, setCurrentStep] = useState(0);
const [totalSteps, setTotalSteps] = useState(0);
const [currentPageTitle, setCurrentPageTitle] = useState("");
const [path, setPath] = useState<Bip44Path>({
const [bip44Path, setBip44Path] = useState<Bip44Path>({
account: 0,
change: 0,
index: 0,
});
const [zip32Path, setZip32Path] = useState<Zip32Path>({
account: 0,
});

const [parentAccountStore, setParentAccountStore] = useState<AccountStore>();
const [shieldedAccount, setShieldedAccount] = useState<DerivedAccount>();
Expand Down Expand Up @@ -251,7 +254,7 @@ export const Setup: React.FC = () => {
if (accountSecret) {
void storeAccount({
...accountCreationDetails,
path,
path: bip44Path,
accountSecret,
flow: "create",
});
Expand All @@ -273,7 +276,8 @@ export const Setup: React.FC = () => {
alias={accountCreationDetails.alias || ""}
accountSecret={selectedAccountSecret}
password={accountCreationDetails.password || ""}
path={path}
path={bip44Path}
// TODO: Display custom zip32 path across apps!
parentAccountStore={parentAccountStore}
shieldedAccount={shieldedAccount}
status={completionStatus}
Expand All @@ -298,8 +302,10 @@ export const Setup: React.FC = () => {
element={
<Wrapper onLoad={setCurrentPage("Import Existing Keys", 1)}>
<SeedPhraseImport
path={path}
setPath={setPath}
bip44Path={bip44Path}
zip32Path={zip32Path}
setBip44Path={setBip44Path}
setZip32Path={setZip32Path}
onConfirm={(accountSecret: AccountSecret) => {
setAccountSecret(accountSecret);
navigate(routes.accountImportCreate());
Expand Down Expand Up @@ -328,7 +334,7 @@ export const Setup: React.FC = () => {
if (accountSecret) {
void storeAccount({
...accountCreationDetails,
path,
path: bip44Path,
accountSecret,
flow: "import",
});
Expand All @@ -350,7 +356,8 @@ export const Setup: React.FC = () => {
alias={accountCreationDetails.alias || ""}
accountSecret={selectedAccountSecret}
password={accountCreationDetails.password || ""}
path={path}
path={bip44Path}
// TODO: Pass zip32 path!
parentAccountStore={parentAccountStore}
shieldedAccount={shieldedAccount}
status={completionStatus}
Expand All @@ -375,7 +382,12 @@ export const Setup: React.FC = () => {
<Wrapper
onLoad={setCurrentPage("Connect Your Ledger Hardware", 1)}
>
<LedgerConnect path={path} setPath={setPath} />
<LedgerConnect
bip44Path={bip44Path}
zip32Path={zip32Path}
setBip44Path={setBip44Path}
setZip32Path={setZip32Path}
/>
</Wrapper>
}
/>
Expand All @@ -389,7 +401,8 @@ export const Setup: React.FC = () => {
)}
>
<LedgerImport
path={path}
bip44Path={bip44Path}
zip32Path={zip32Path}
passwordRequired={!passwordInitialized}
/>
</Wrapper>
Expand Down
1 change: 1 addition & 0 deletions apps/extension/src/storage/schemas/VaultSchemaV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const KeyStore = t.exact(
timestamp: t.number,
}),
t.partial({
modifiedZip32Path: t.string,
publicKey: t.string,
parentId: t.string,
pseudoExtendedKey: t.string,
Expand Down

0 comments on commit 8750cfe

Please sign in to comment.