Skip to content

Commit

Permalink
allow mnemonic creation for accounts onboarded with ledger (#3224)
Browse files Browse the repository at this point in the history
* wip

* force close parent and spacing tweak
  • Loading branch information
tomlinton authored Mar 10, 2023
1 parent 823a0d3 commit 1262caa
Show file tree
Hide file tree
Showing 18 changed files with 479 additions and 254 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ export const OnboardAccount = ({
key="MnemonicInput"
readOnly={action === "create"}
buttonLabel={action === "create" ? "Next" : "Import"}
onNext={(mnemonic) => {
setOnboardingData({ mnemonic });
nextStep();
}}
/>,
]
onNext={async (mnemonic) => {
setOnboardingData({ mnemonic });
nextStep();
}}
/>,
]
: []),
<BlockchainSelector
key="BlockchainSelector"
Expand All @@ -127,11 +127,11 @@ export const OnboardAccount = ({
<CreatePassword
key="CreatePassword"
onNext={async (password) => {
setOnboardingData({ password });
nextStep();
}}
/>,
]
setOnboardingData({ password });
nextStep();
}}
/>,
]
: []),
<NotificationsPermission key="NotificationsPermission" onNext={nextStep} />,
<Finish key="Finish" isAddingAccount={isAddingAccount} />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,43 +87,44 @@ export const RecoverAccount = ({
/>,
...(keyringType === "mnemonic"
? [
// Using a mnemonic
// Using a mnemonic
<MnemonicInput
key="MnemonicInput"
buttonLabel="Next"
onNext={(mnemonic: string) => {
setOnboardingData({ mnemonic });
nextStep();
}}
/>,
onNext={async (mnemonic: string) => {
setOnboardingData({ mnemonic });
nextStep();
}}
/>,
<MnemonicSearch
key="MnemonicSearch"
serverPublicKeys={serverPublicKeys!}
mnemonic={mnemonic!}
onNext={async (walletDescriptors: Array<WalletDescriptor>) => {
const signedWalletDescriptors = await Promise.all(
walletDescriptors.map(async (w) => ({
...w,
signature: await signMessageForWallet(w, authMessage),
}))
);
setOnboardingData({ signedWalletDescriptors });
nextStep();
}}
const signedWalletDescriptors = await Promise.all(
walletDescriptors.map(async (w) => ({
...w,
signature: await signMessageForWallet(w, authMessage),
}))
);
console.log(signedWalletDescriptors)
setOnboardingData({ signedWalletDescriptors });
nextStep();
}}
onRetry={prevStep}
/>,
]
/>,
]
: hardwareOnboardSteps),
...(!isAddingAccount
? [
<CreatePassword
key="CreatePassword"
onNext={async (password) => {
setOnboardingData({ password });
nextStep();
}}
/>,
]
setOnboardingData({ password });
nextStep();
}}
/>,
]
: []),
...(signedWalletDescriptors.length > 0
? [<Finish key="Finish" isAddingAccount={isAddingAccount} />]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,62 +98,60 @@ export function CreateMenu({ blockchain }: { blockchain: Blockchain }) {
if (loading) {
return;
}
setOpenDrawer(true);
setLoading(true);
let newPublicKey;
if (!keyringExists || !hasHdPublicKeys) {
// No keyring or no existing mnemonic public keys so can't derive next
const walletDescriptor = await background.request({
method: UI_RPC_METHOD_FIND_WALLET_DESCRIPTOR,
params: [blockchain, 0],
});
const signature = await background.request({
method: UI_RPC_METHOD_SIGN_MESSAGE_FOR_PUBLIC_KEY,
params: [
blockchain,
walletDescriptor.publicKey,
base58.encode(
Buffer.from(getAddMessage(walletDescriptor.publicKey), "utf-8")
),
[true, [walletDescriptor.derivationPath]],
],
});
if (hasMnemonic) {
setOpenDrawer(true);
setLoading(true);
let newPublicKey;
if (!keyringExists || !hasHdPublicKeys) {
// No keyring or no existing mnemonic public keys so can't derive next
const walletDescriptor = await background.request({
method: UI_RPC_METHOD_FIND_WALLET_DESCRIPTOR,
params: [blockchain, 0],
});
const signature = await background.request({
method: UI_RPC_METHOD_SIGN_MESSAGE_FOR_PUBLIC_KEY,
params: [
blockchain,
walletDescriptor.publicKey,
base58.encode(
Buffer.from(getAddMessage(walletDescriptor.publicKey), "utf-8")
),
[true, [walletDescriptor.derivationPath]],
],
});
await background.request({
method: UI_RPC_METHOD_BLOCKCHAIN_KEYRINGS_ADD,
params: [blockchain, { ...walletDescriptor, signature }],
});
newPublicKey = walletDescriptor.publicKey;
// Keyring now exists, toggle to other options
setKeyringExists(true);
} else {
newPublicKey = await background.request({
method: UI_RPC_METHOD_KEYRING_DERIVE_WALLET,
params: [blockchain],
});
}
await background.request({
method: UI_RPC_METHOD_BLOCKCHAIN_KEYRINGS_ADD,
params: [{ ...walletDescriptor, signature }],
method: UI_RPC_METHOD_USER_ACCOUNT_READ,
params: [authenticatedUser?.jwt],
});
newPublicKey = walletDescriptor.publicKey;
// Keyring now exists, toggle to other options
setKeyringExists(true);
setNewPublicKey(newPublicKey);
setLoading(false);
} else {
newPublicKey = await background.request({
method: UI_RPC_METHOD_KEYRING_DERIVE_WALLET,
params: [blockchain],
});
nav.push("create-mnemonic", {
blockchain,
keyringExists
})
}

await background.request({
method: UI_RPC_METHOD_USER_ACCOUNT_READ,
params: [authenticatedUser?.jwt],
});

setNewPublicKey(newPublicKey);
setLoading(false);
};

const createMenu = {
...(hasMnemonic
? // TODO user should be guided through mnemonic creation flow if
// they don't have a mnemonic
// https://github.com/coral-xyz/backpack/issues/1464
{
"Secret recovery phrase": {
onClick: createNewWithPhrase,
icon: (props: any) => <MnemonicIcon {...props} />,
detailIcon: <PushDetail />,
},
}
: {}),
"Secret recovery phrase": {
onClick: createNewWithPhrase,
icon: (props: any) => <MnemonicIcon {...props} />,
detailIcon: <PushDetail />,
},
"Hardware wallet": {
onClick: () => {
openConnectHardware(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { useEffect, useState } from "react";
import type {
Blockchain,
SignedWalletDescriptor,
} from "@coral-xyz/common";
import {
getAddMessage,
UI_RPC_METHOD_BLOCKCHAIN_KEYRINGS_ADD,
UI_RPC_METHOD_FIND_WALLET_DESCRIPTOR,
UI_RPC_METHOD_KEYRING_IMPORT_WALLET,
UI_RPC_METHOD_KEYRING_SET_MNEMONIC,
UI_RPC_METHOD_SIGN_MESSAGE_FOR_PUBLIC_KEY
} from "@coral-xyz/common";
import {
useBackgroundClient,
} from "@coral-xyz/recoil";
import { useCustomTheme } from "@coral-xyz/themes";
import { ethers } from "ethers";

import { MnemonicInput } from "../../../common/Account/MnemonicInput";
import {
useDrawerContext,
WithMiniDrawer,
} from "../../../common/Layout/Drawer";
import { useNavigation } from "../../../common/Layout/NavStack";

const { base58 } = ethers.utils;

import { ConfirmCreateWallet } from "./";

export function CreateMnemonic({
blockchain,
keyringExists,
}: {
blockchain: Blockchain;
keyringExists: boolean;
}) {
const nav = useNavigation();
const theme = useCustomTheme();
const background = useBackgroundClient();
const { close: closeParentDrawer } = useDrawerContext();

const [openDrawer, setOpenDrawer] = useState(false);
const [publicKey, setPublicKey] = useState<string | null>(null);

useEffect(() => {
const prevTitle = nav.title;
nav.setOptions({ headerTitle: "" });
return () => {
nav.setOptions({ headerTitle: prevTitle });
};
}, [theme]);

// TODO replace the left nav button to go to the previous step if step > 0

const onComplete = async (mnemonic: string, signedWalletDescriptor: SignedWalletDescriptor) => {
let publicKey: string;
await background.request({
method: UI_RPC_METHOD_KEYRING_SET_MNEMONIC,
params: [mnemonic]
})
if (keyringExists) {
// Using the keyring mnemonic and the blockchain keyring exists, just
// import the path
publicKey = await background.request({
method: UI_RPC_METHOD_KEYRING_IMPORT_WALLET,
params: [blockchain, signedWalletDescriptor],
});
} else {
// Blockchain keyring doesn't exist, init
publicKey = await background.request({
method: UI_RPC_METHOD_BLOCKCHAIN_KEYRINGS_ADD,
params: [blockchain, signedWalletDescriptor],
});
}
setPublicKey(publicKey);
setOpenDrawer(true);
};

return (
<>
<MnemonicInput
readOnly
buttonLabel="Next"
subtitle="Write it down and store it in a safe place."
onNext={async (mnemonic: string) => {
const walletDescriptor = await background.request({
method: UI_RPC_METHOD_FIND_WALLET_DESCRIPTOR,
params: [blockchain, 0, mnemonic],
});

const signature = await background.request({
method: UI_RPC_METHOD_SIGN_MESSAGE_FOR_PUBLIC_KEY,
params: [
blockchain,
walletDescriptor.publicKey,
base58.encode(
Buffer.from(
getAddMessage(walletDescriptor.publicKey),
"utf-8"
)
),
[mnemonic, [walletDescriptor.derivationPath]],
]
})

await onComplete(mnemonic, {
...walletDescriptor,
signature
})
}}
/>
<WithMiniDrawer
openDrawer={openDrawer}
setOpenDrawer={(open: boolean) => {
// Must close parent when the confirm create wallet drawer closes because
// the next button in the mnemonic input screen is no longer valid as the users
// keyring has a mnemonic once it has been clicked once
if (!open) closeParentDrawer()
setOpenDrawer(open)
}}
backdropProps={{
style: {
opacity: 0.8,
background: "#18181b",
},
}}
>
<ConfirmCreateWallet
blockchain={blockchain}
publicKey={publicKey!}
onClose={() => {
setOpenDrawer(false);
closeParentDrawer();
}}
/>
</WithMiniDrawer>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,21 +108,21 @@ export function ImportMnemonic({
<MnemonicInput
key="MnemonicInput"
buttonLabel="Next"
onNext={(mnemonic) => {
setMnemonic(mnemonic);
nextStep();
}}
/>,
// Must prompt for a name if using an input mnemonic, because we can't
// easily generate one
onNext={async (mnemonic) => {
setMnemonic(mnemonic);
nextStep();
}}
/>,
// Must prompt for a name if using an input mnemonic, because we can't
// easily generate one
<InputName
key="InputName"
onNext={(name) => {
setName(name);
nextStep();
}}
/>,
]
setName(name);
nextStep();
}}
/>,
]
: []),
<ImportWallets
key="ImportWallets"
Expand Down
Loading

1 comment on commit 1262caa

@vercel
Copy link

@vercel vercel bot commented on 1262caa Mar 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.