diff --git a/packages/app-extension/src/components/Onboarding/pages/Finish.tsx b/packages/app-extension/src/components/Onboarding/pages/Finish.tsx index 08c85da5f..34248c931 100644 --- a/packages/app-extension/src/components/Onboarding/pages/Finish.tsx +++ b/packages/app-extension/src/components/Onboarding/pages/Finish.tsx @@ -43,7 +43,7 @@ export const Finish = ({ isAddingAccount }: { isAddingAccount?: boolean }) => { } setLoading(false); })(); - }, [onboardingData, isAddingAccount]); + }, [background, isAddingAccount, onboardingData, maybeCreateUser]); return !loading ? ( { @@ -92,6 +92,7 @@ export const HardwareDefaultWallet = ({ const publicKey = publicKeys[0]; const derivationPath = recoveryPaths[0]; onNext({ + blockchain, derivationPath, publicKey, }); @@ -100,7 +101,7 @@ export const HardwareDefaultWallet = ({ setAccountIndex(accountIndex + 1); } })(); - }, [ledgerWallet, accountIndex]); + }, [accountIndex, background, blockchain, ledgerWallet, onError, onNext]); return ; }; diff --git a/packages/app-extension/src/components/Onboarding/pages/HardwareDeriveWallet.tsx b/packages/app-extension/src/components/Onboarding/pages/HardwareDeriveWallet.tsx index d66d23007..554c6c2b0 100644 --- a/packages/app-extension/src/components/Onboarding/pages/HardwareDeriveWallet.tsx +++ b/packages/app-extension/src/components/Onboarding/pages/HardwareDeriveWallet.tsx @@ -37,7 +37,7 @@ export const HardwareDeriveWallet = ({ }[blockchain]; setLedgerWallet(ledgerWallet); })(); - }, [blockchain]); + }, [blockchain, transport]); useEffect(() => { (async () => { @@ -71,11 +71,12 @@ export const HardwareDeriveWallet = ({ // path if unusable onNext({ + blockchain, derivationPath, publicKey, }); })(); - }, [ledgerWallet]); + }, [background, blockchain, ledgerWallet, onError, onNext]); return ; }; diff --git a/packages/app-extension/src/components/Onboarding/pages/HardwareOnboard.tsx b/packages/app-extension/src/components/Onboarding/pages/HardwareOnboard.tsx index 0910b859b..4f5b10099 100644 --- a/packages/app-extension/src/components/Onboarding/pages/HardwareOnboard.tsx +++ b/packages/app-extension/src/components/Onboarding/pages/HardwareOnboard.tsx @@ -52,8 +52,9 @@ export function useHardwareOnboardSteps({ // Flow for onboarding a hardware wallet. // const steps = [ - , + , { setTransport(transport); @@ -141,6 +142,7 @@ export function useHardwareOnboardSteps({ ? [ // Sign the found wallet descriptor for API submit ; diff --git a/packages/app-extension/src/components/Onboarding/pages/MnemonicSearch.tsx b/packages/app-extension/src/components/Onboarding/pages/MnemonicSearch.tsx index 24eea1855..4186e7086 100644 --- a/packages/app-extension/src/components/Onboarding/pages/MnemonicSearch.tsx +++ b/packages/app-extension/src/components/Onboarding/pages/MnemonicSearch.tsx @@ -47,6 +47,7 @@ export const MnemonicSearch = ({ const index = publicKeys.findIndex((p: string) => p === publicKey); if (index !== -1) { walletDescriptors.push({ + blockchain, derivationPath: recoveryPaths[index], publicKey, }); @@ -59,7 +60,7 @@ export const MnemonicSearch = ({ setError(true); } })(); - }, [serverPublicKeys, mnemonic]); + }, [background, serverPublicKeys, mnemonic, onNext]); if (!error) { return ; diff --git a/packages/app-extension/src/components/Onboarding/pages/OnboardAccount.tsx b/packages/app-extension/src/components/Onboarding/pages/OnboardAccount.tsx index c946d41b5..057205f30 100644 --- a/packages/app-extension/src/components/Onboarding/pages/OnboardAccount.tsx +++ b/packages/app-extension/src/components/Onboarding/pages/OnboardAccount.tsx @@ -4,15 +4,8 @@ import type { SignedWalletDescriptor, WalletDescriptor, } from "@coral-xyz/common"; -import { - getCreateMessage, - UI_RPC_METHOD_KEYRING_STORE_KEEP_ALIVE, -} from "@coral-xyz/common"; -import { - useBackgroundClient, - useOnboarding, - useSignMessageForWallet, -} from "@coral-xyz/recoil"; +import { getCreateMessage } from "@coral-xyz/common"; +import { useOnboarding, useSignMessageForWallet } from "@coral-xyz/recoil"; import { useSteps } from "../../../hooks/useSteps"; import { CreatePassword } from "../../common/Account/CreatePassword"; @@ -59,6 +52,7 @@ export const OnboardAccount = ({ signedWalletDescriptors, selectedBlockchains, } = onboardingData; + const signMessageForWallet = useSignMessageForWallet(mnemonic); useEffect(() => { @@ -66,7 +60,7 @@ export const OnboardAccount = ({ setOnboardingData({ signedWalletDescriptors: [], }); - }, [action, keyringType, mnemonic]); + }, [action, keyringType, mnemonic, setOnboardingData]); const steps = [ { const prevTitle = nav.title; @@ -77,7 +77,7 @@ export function CreateMenu({ blockchain }: { blockchain: Blockchain }) { return () => { nav.setOptions({ headerTitle: prevTitle }); }; - }, [nav.setOptions]); + }, [nav]); useEffect(() => { (async () => { @@ -87,7 +87,7 @@ export function CreateMenu({ blockchain }: { blockchain: Blockchain }) { }); setKeyringExists(blockchainKeyrings.includes(blockchain)); })(); - }, [blockchain]); + }, [background, blockchain]); const createNewWithPhrase = async () => { // Mnemonic based keyring. This is the simple case because we don't @@ -120,7 +120,7 @@ export function CreateMenu({ blockchain }: { blockchain: Blockchain }) { }); await background.request({ method: UI_RPC_METHOD_BLOCKCHAIN_KEYRINGS_ADD, - params: [blockchain, { ...walletDescriptor, signature }], + params: [{ ...walletDescriptor, signature }], }); newPublicKey = walletDescriptor.publicKey; // Keyring now exists, toggle to other options diff --git a/packages/app-extension/src/components/Unlocked/Settings/AddConnectWallet/ImportMnemonic.tsx b/packages/app-extension/src/components/Unlocked/Settings/AddConnectWallet/ImportMnemonic.tsx index 68502ef0b..924c50734 100644 --- a/packages/app-extension/src/components/Unlocked/Settings/AddConnectWallet/ImportMnemonic.tsx +++ b/packages/app-extension/src/components/Unlocked/Settings/AddConnectWallet/ImportMnemonic.tsx @@ -62,7 +62,7 @@ export function ImportMnemonic({ return () => { nav.setOptions({ headerTitle: prevTitle }); }; - }, [theme]); + }, [nav, theme]); // TODO replace the left nav button to go to the previous step if step > 0 @@ -74,13 +74,13 @@ export function ImportMnemonic({ // import the path pk = await background.request({ method: UI_RPC_METHOD_KEYRING_IMPORT_WALLET, - params: [blockchain, signedWalletDescriptor], + params: [signedWalletDescriptor], }); } else { // Blockchain keyring doesn't exist, init pk = await background.request({ method: UI_RPC_METHOD_BLOCKCHAIN_KEYRINGS_ADD, - params: [blockchain, signedWalletDescriptor], + params: [signedWalletDescriptor], }); } } else { @@ -106,6 +106,7 @@ export function ImportMnemonic({ ...(inputMnemonic ? [ { setMnemonic(mnemonic); @@ -115,6 +116,7 @@ export function ImportMnemonic({ // Must prompt for a name if using an input mnemonic, because we can't // easily generate one { setName(name); nextStep(); @@ -123,7 +125,8 @@ export function ImportMnemonic({ ] : []), { setWalletDescriptors( derivationPaths.map((derivationPath, i) => ({ + blockchain, publicKey: publicKeys[i], derivationPath, })) @@ -388,9 +389,10 @@ export function ImportWallets({ if (currentIndex === -1) { // Not selected, add it const walletDescriptor = { + blockchain, derivationPath, publicKey, - } as WalletDescriptor; + }; // Adding the account if (allowMultiple) { newCheckedWalletDescriptors.push(walletDescriptor); @@ -448,8 +450,8 @@ export function ImportWallets({ select disabled={ledgerLocked} > - {derivationPathOptions.map((o, index) => ( - + {derivationPathOptions.map((o) => ( + {o.label} ))} diff --git a/packages/background/src/backend/core.ts b/packages/background/src/backend/core.ts index c72fbb7d9..8bc6a149a 100644 --- a/packages/background/src/backend/core.ts +++ b/packages/background/src/backend/core.ts @@ -1180,13 +1180,10 @@ export class Backend { return SUCCESS_RESPONSE; } - async ledgerImport( - blockchain: Blockchain, - signedWalletDescriptor: SignedWalletDescriptor - ) { + async ledgerImport(signedWalletDescriptor: SignedWalletDescriptor) { const { signature, ...walletDescriptor } = signedWalletDescriptor; - const { publicKey } = walletDescriptor; - await this.keyringStore.ledgerImport(blockchain, walletDescriptor); + const { blockchain, publicKey } = walletDescriptor; + await this.keyringStore.ledgerImport(walletDescriptor); try { await this.userAccountPublicKeyCreate(blockchain, publicKey, signature); } catch (error) { @@ -1259,7 +1256,7 @@ export class Backend { publicKey: publicKeys[index], derivationPath: recoveryPaths[index], }; - await this.blockchainKeyringsAdd(blockchain, { + await this.blockchainKeyringsAdd({ ...walletDescriptor, signature: "", }); @@ -1549,6 +1546,7 @@ export class Backend { const publicKey = publicKeys[0]; const derivationPath = recoveryPaths[0]; return { + blockchain, derivationPath, publicKey, }; @@ -1681,15 +1679,13 @@ export class Backend { * Add a new blockchain keyring to the keyring store (i.e. initialize it). */ async blockchainKeyringsAdd( - blockchain: Blockchain, signedWalletDescriptor: SignedWalletDescriptor ): Promise { await this.keyringStore.blockchainKeyringAdd( - blockchain, signedWalletDescriptor as WalletDescriptor ); - const { signature, publicKey } = signedWalletDescriptor; + const { blockchain, signature, publicKey } = signedWalletDescriptor; // Add the new public key to the API try { diff --git a/packages/background/src/backend/keyring/index.ts b/packages/background/src/backend/keyring/index.ts index 52e3ec5a8..c6d359ac9 100644 --- a/packages/background/src/backend/keyring/index.ts +++ b/packages/background/src/backend/keyring/index.ts @@ -15,7 +15,6 @@ import { BACKEND_EVENT, DEFAULT_AUTO_LOCK_INTERVAL_SECS, defaultPreferences, - getBlockchainFromPath, NOTIFICATION_KEYRING_STORE_LOCKED, } from "@coral-xyz/common"; import type { KeyringStoreState } from "@coral-xyz/recoil"; @@ -408,14 +407,10 @@ export class KeyringStore { * Initialise a blockchain keyring. */ public async blockchainKeyringAdd( - blockchain: Blockchain, walletDescriptor: WalletDescriptor, persist = true ): Promise { - await this.activeUserKeyring.blockchainKeyringAdd( - blockchain, - walletDescriptor - ); + await this.activeUserKeyring.blockchainKeyringAdd(walletDescriptor); if (persist) { await this.persist(); } @@ -491,15 +486,9 @@ export class KeyringStore { }); } - public async ledgerImport( - blockchain: Blockchain, - walletDescriptor: WalletDescriptor - ) { + public async ledgerImport(walletDescriptor: WalletDescriptor) { return await this.withUnlockAndPersist(async () => { - return await this.activeUserKeyring.ledgerImport( - blockchain, - walletDescriptor - ); + return await this.activeUserKeyring.ledgerImport(walletDescriptor); }); } @@ -668,7 +657,7 @@ class UserKeyring { keyring.mnemonic = keyringInit.mnemonic; for (const signedWalletDescriptor of keyringInit.signedWalletDescriptors) { const blockchainKeyring = keyring.blockchains.get( - getBlockchainFromPath(signedWalletDescriptor.derivationPath) + signedWalletDescriptor.blockchain ); if (blockchainKeyring) { // Blockchain keyring already exists, just add the derivation path @@ -677,16 +666,12 @@ class UserKeyring { ); } else { // No blockchain keyring, create it - await keyring.blockchainKeyringAdd( - getBlockchainFromPath(signedWalletDescriptor.derivationPath), - signedWalletDescriptor - ); + await keyring.blockchainKeyringAdd(signedWalletDescriptor); } } // Set the active wallet to be the first keyring. - keyring.activeBlockchain = getBlockchainFromPath( - keyringInit.signedWalletDescriptors[0].derivationPath - ); + keyring.activeBlockchain = + keyringInit.signedWalletDescriptors[0].blockchain; return keyring; } @@ -768,10 +753,9 @@ class UserKeyring { /////////////////////////////////////////////////////////////////////////////// public async blockchainKeyringAdd( - blockchain: Blockchain, walletDescriptor: WalletDescriptor ): Promise { - const keyring = keyringForBlockchain(blockchain); + const keyring = keyringForBlockchain(walletDescriptor.blockchain); if (this.mnemonic) { // Initialising using a mnemonic await keyring.initFromMnemonic(this.mnemonic, [ @@ -781,7 +765,7 @@ class UserKeyring { // Initialising using a hardware wallet await keyring.initFromLedger([walletDescriptor]); } - this.blockchains.set(blockchain, keyring); + this.blockchains.set(walletDescriptor.blockchain, keyring); return walletDescriptor.publicKey; } @@ -860,11 +844,8 @@ class UserKeyring { return this.mnemonic; } - public async ledgerImport( - blockchain: Blockchain, - walletDescriptor: WalletDescriptor - ) { - const blockchainKeyring = this.blockchains.get(blockchain); + public async ledgerImport(walletDescriptor: WalletDescriptor) { + const blockchainKeyring = this.blockchains.get(walletDescriptor.blockchain); const ledgerKeyring = blockchainKeyring!.ledgerKeyring!; await ledgerKeyring.add(walletDescriptor); const name = DefaultKeyname.defaultLedger( diff --git a/packages/background/src/frontend/server-ui.ts b/packages/background/src/frontend/server-ui.ts index a549b627e..ca1b8520b 100644 --- a/packages/background/src/frontend/server-ui.ts +++ b/packages/background/src/frontend/server-ui.ts @@ -1110,13 +1110,9 @@ async function handleSetXnftPreferences( async function handleBlockchainKeyringsAdd( ctx: Context, - blockchain: Blockchain, signedWalletDescriptor: SignedWalletDescriptor ): Promise>> { - const resp = await ctx.backend.blockchainKeyringsAdd( - blockchain, - signedWalletDescriptor - ); + const resp = await ctx.backend.blockchainKeyringsAdd(signedWalletDescriptor); return [resp]; } diff --git a/packages/blockchains/solana/src/keyring/index.ts b/packages/blockchains/solana/src/keyring/index.ts index 923fe9c66..309f90865 100644 --- a/packages/blockchains/solana/src/keyring/index.ts +++ b/packages/blockchains/solana/src/keyring/index.ts @@ -177,7 +177,7 @@ class SolanaHdKeyring extends SolanaKeyring implements HdKeyring { public nextDerivationPath(offset = 1) { this.ensureIndices(); const derivationPath = getIndexedPath( - Blockchain.ETHEREUM, + Blockchain.SOLANA, this.accountIndex, this.walletIndex! + offset ); diff --git a/packages/common/src/crypto.test.ts b/packages/common/src/crypto.test.ts index 7275b387f..671567a9a 100644 --- a/packages/common/src/crypto.test.ts +++ b/packages/common/src/crypto.test.ts @@ -1,4 +1,8 @@ -import { getIndexedPath, nextIndicesFromPaths } from "./crypto"; +import { + getAccountRecoveryPaths, + getIndexedPath, + nextIndicesFromPaths, +} from "./crypto"; import { Blockchain } from "./types"; test("gets correct account index for m/44'/501'", () => { @@ -80,3 +84,19 @@ test("gets correct next path after m/44'/501'/9'/0'/2'", () => { "m/44'/501'/9'/0'/3'" ); }); + +test("gets correct account recovery paths for Solana", () => { + const recoveryPaths = getAccountRecoveryPaths(Blockchain.SOLANA, 0); + expect(recoveryPaths[0]).toEqual("m/44'/501'/0'/0'"); + expect(recoveryPaths[1]).toEqual("m/44'/501'/0'/0'/0'"); + expect(recoveryPaths[2]).toEqual("m/44'/501'/0'/0'/1'"); + expect(recoveryPaths[3]).toEqual("m/44'/501'/0'/0'/2'"); +}); + +test("gets correct account recovery paths for Ethereum", () => { + const recoveryPaths = getAccountRecoveryPaths(Blockchain.ETHEREUM, 0); + expect(recoveryPaths[0]).toEqual("m/44'/60'/0'/0'"); + expect(recoveryPaths[1]).toEqual("m/44'/60'/0'/0'/0'"); + expect(recoveryPaths[2]).toEqual("m/44'/60'/0'/0'/1'"); + expect(recoveryPaths[3]).toEqual("m/44'/60'/0'/0'/2'"); +}); diff --git a/packages/common/src/crypto.ts b/packages/common/src/crypto.ts index a4f149efe..bda62cdbe 100644 --- a/packages/common/src/crypto.ts +++ b/packages/common/src/crypto.ts @@ -19,15 +19,6 @@ export const getCoinType = (blockchain: Blockchain) => { return coinType + HARDENING; }; -export const getBlockchainFromPath = (derivationPath: string): Blockchain => { - const coinType = BIPPath.fromString(derivationPath).toPathArray()[1]; - return Object.keys(blockchainCoinType).find( - (key) => - blockchainCoinType[key] === coinType || - blockchainCoinType[key] === coinType - HARDENING - ) as Blockchain; -}; - export const legacyBip44Indexed = (blockchain: Blockchain, index: number) => { const coinType = getCoinType(blockchain); const path = [44 + HARDENING, coinType]; @@ -120,8 +111,8 @@ export const getAccountRecoveryPaths = ( blockchain: Blockchain, accountIndex: number ) => { - return [...Array(LOAD_PUBLIC_KEY_AMOUNT).keys()].map((j) => - getIndexedPath(blockchain, accountIndex, j) + return [...Array(LOAD_PUBLIC_KEY_AMOUNT + 1).keys()].map((j) => + getIndexedPath(blockchain, accountIndex, j - 1) ); }; @@ -207,7 +198,15 @@ export const getRecoveryPaths = (blockchain: Blockchain) => { // Legacy imported accounts (m/44/501'/0' and m/44/501'/0'/{0...n}) paths = paths.concat(legacyBip44ChangeRecoveryPaths(blockchain)); - if (blockchain === Blockchain.ETHEREUM) { + if (blockchain === Blockchain.SOLANA) { + // Handle legacy Solana wallets that were created in 0.5.0 that had + // Ethereum derivation paths + paths = paths.concat( + getAccountRecoveryPaths(Blockchain.SOLANA, 0).map((d) => + d.replace("501", "60") + ) + ); + } else if (blockchain === Blockchain.ETHEREUM) { paths = paths.concat( [...Array(LOAD_PUBLIC_KEY_AMOUNT).keys()].map(ethereumIndexed) ); diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index ea16a83d3..f63116a5b 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -105,6 +105,7 @@ export type KeyringInit = { // Location of a public key including the public key export type WalletDescriptor = { + blockchain: Blockchain; derivationPath: string; publicKey: string; }; diff --git a/packages/recoil/src/context/OnboardingProvider.tsx b/packages/recoil/src/context/OnboardingProvider.tsx index b3a82e8e6..e51bc9358 100644 --- a/packages/recoil/src/context/OnboardingProvider.tsx +++ b/packages/recoil/src/context/OnboardingProvider.tsx @@ -14,7 +14,6 @@ import { BACKEND_API_URL, Blockchain, getAuthMessage, - getBlockchainFromPath, getCreateMessage, UI_RPC_METHOD_FIND_WALLET_DESCRIPTOR, UI_RPC_METHOD_KEYRING_STORE_CREATE, @@ -147,8 +146,8 @@ export function OnboardingProvider({ selectedBlockchains: data.signedWalletDescriptors ? [ ...new Set( - data.signedWalletDescriptors.map((s: SignedWalletDescriptor) => - getBlockchainFromPath(s.derivationPath) + data.signedWalletDescriptors.map( + (s: SignedWalletDescriptor) => s.blockchain ) ), ] @@ -171,7 +170,7 @@ export function OnboardingProvider({ setOnboardingData({ blockchain: null, signedWalletDescriptors: signedWalletDescriptors.filter( - (s) => getBlockchainFromPath(s.derivationPath) !== blockchain + (s) => s.blockchain !== blockchain ), }); } else { @@ -238,11 +237,11 @@ export function OnboardingProvider({ // Authenticate the user that the recovery has a JWT. // Take the first keyring init to fetch the JWT, it doesn't matter which // we use if there are multiple. - const { derivationPath, publicKey, signature } = + const { blockchain, publicKey, signature } = keyringInit.signedWalletDescriptors[0]; const authData = { - blockchain: getBlockchainFromPath(derivationPath), + blockchain: blockchain!, publicKey, signature, message: getAuthMessage(userId), @@ -264,11 +263,7 @@ export function OnboardingProvider({ username, inviteCode, waitlistId: getWaitlistId?.(), - blockchainPublicKeys: keyringInit.signedWalletDescriptors.map((b) => ({ - blockchain: getBlockchainFromPath(b.derivationPath), - publicKey: b.publicKey, - signature: b.signature, - })), + blockchainPublicKeys: keyringInit.signedWalletDescriptors, }); try { diff --git a/packages/recoil/src/hooks/useSignMessageForWallet.tsx b/packages/recoil/src/hooks/useSignMessageForWallet.tsx index 20c61e0d4..bfc94e75e 100644 --- a/packages/recoil/src/hooks/useSignMessageForWallet.tsx +++ b/packages/recoil/src/hooks/useSignMessageForWallet.tsx @@ -1,8 +1,5 @@ import type { WalletDescriptor } from "@coral-xyz/common"; -import { - getBlockchainFromPath, - UI_RPC_METHOD_SIGN_MESSAGE_FOR_PUBLIC_KEY, -} from "@coral-xyz/common"; +import { UI_RPC_METHOD_SIGN_MESSAGE_FOR_PUBLIC_KEY } from "@coral-xyz/common"; import { ethers } from "ethers"; import { useBackgroundClient } from "./"; @@ -14,11 +11,10 @@ export const useSignMessageForWallet = (mnemonic?: string | true) => { walletDescriptor: WalletDescriptor, message: string ) => { - const blockchain = getBlockchainFromPath(walletDescriptor.derivationPath); return await background.request({ method: UI_RPC_METHOD_SIGN_MESSAGE_FOR_PUBLIC_KEY, params: [ - blockchain, + walletDescriptor.blockchain, walletDescriptor.publicKey, ethers.utils.base58.encode(Buffer.from(message, "utf-8")), [mnemonic, [walletDescriptor.derivationPath]],