From f028023c1da712d229abf222cceed75b077acb60 Mon Sep 17 00:00:00 2001 From: gregs Date: Tue, 26 Mar 2024 20:03:11 -0300 Subject: [PATCH 1/3] improve type safety of keychain manager --- src/core/keychain/KeychainManager.test.ts | 7 +++-- src/core/keychain/KeychainManager.ts | 30 +++++++------------ .../keychainTypes/hardwareWalletKeychain.ts | 6 ++-- src/core/keychain/keychainTypes/hdKeychain.ts | 5 ++-- .../keychain/keychainTypes/keyPairKeychain.ts | 5 ++-- .../keychainTypes/readOnlyKeychain.ts | 8 ++--- 6 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/core/keychain/KeychainManager.test.ts b/src/core/keychain/KeychainManager.test.ts index 443d159425..51f5a22e75 100644 --- a/src/core/keychain/KeychainManager.test.ts +++ b/src/core/keychain/KeychainManager.test.ts @@ -62,7 +62,7 @@ test('[keychain/KeychainManager] :: should be able to export the seed phrase for test('[keychain/KeychainManager] :: should be able to add a read only wallet using an address', async () => { await keychainManager.importKeychain({ - type: 'ReadOnlyKeychain', + type: KeychainType.ReadOnlyKeychain, address: '0x70c16D2dB6B00683b29602CBAB72CE0Dcbc243C4', }); const accounts = await keychainManager.getAccounts(); @@ -79,7 +79,10 @@ test('[keychain/KeychainManager] :: should be able to remove an account from a R }); test('[keychain/KeychainManager] :: should be able to import a wallet using a private key', async () => { - await keychainManager.importKeychain({ type: 'KeyPairKeychain', privateKey }); + await keychainManager.importKeychain({ + type: KeychainType.KeyPairKeychain, + privateKey, + }); const accounts = await keychainManager.getAccounts(); expect(accounts.length).toBe(2); expect(isAddress(accounts[1])).toBe(true); diff --git a/src/core/keychain/KeychainManager.ts b/src/core/keychain/KeychainManager.ts index 7b9b0d4362..62a3a43811 100644 --- a/src/core/keychain/KeychainManager.ts +++ b/src/core/keychain/KeychainManager.ts @@ -101,28 +101,24 @@ class KeychainManager { this.state.initialized = true; } }, - deriveAccounts: async ( - opts: SerializedKeypairKeychain | SerializedHdKeychain, - ): Promise => { + deriveAccounts: async (opts: SerializedKeychain): Promise => { let keychain; switch (opts.type) { case KeychainType.HdKeychain: keychain = new HdKeychain(); - await keychain.init(opts as SerializedHdKeychain); + await keychain.init(opts); break; case KeychainType.KeyPairKeychain: keychain = new KeyPairKeychain(); - await keychain.init(opts as SerializedKeypairKeychain); + await keychain.init(opts); break; case KeychainType.ReadOnlyKeychain: keychain = new ReadOnlyKeychain(); - await keychain.init(opts as unknown as SerializedReadOnlyKeychain); + await keychain.init(opts); break; case KeychainType.HardwareWalletKeychain: keychain = new HardwareWalletKeychain(); - await keychain.init( - opts as unknown as SerializedHardwareWalletKeychain, - ); + await keychain.init(opts); break; default: throw new Error('Keychain type not recognized.'); @@ -134,19 +130,19 @@ class KeychainManager { switch (opts.type) { case KeychainType.HdKeychain: keychain = new HdKeychain(); - await keychain.init(opts as SerializedHdKeychain); + await keychain.init(opts); break; case KeychainType.KeyPairKeychain: keychain = new KeyPairKeychain(); - await keychain.init(opts as SerializedKeypairKeychain); + await keychain.init(opts); break; case KeychainType.ReadOnlyKeychain: keychain = new ReadOnlyKeychain(); - await keychain.init(opts as SerializedReadOnlyKeychain); + await keychain.init(opts); break; case KeychainType.HardwareWalletKeychain: keychain = new HardwareWalletKeychain(); - await keychain.init(opts as SerializedHardwareWalletKeychain); + await keychain.init(opts); break; default: throw new Error('Keychain type not recognized.'); @@ -330,13 +326,7 @@ class KeychainManager { return keychain; } - async importKeychain( - opts: - | SerializedKeypairKeychain - | SerializedHdKeychain - | SerializedReadOnlyKeychain - | SerializedHardwareWalletKeychain, - ): Promise { + async importKeychain(opts: SerializedKeychain): Promise { const result = await privates.get(this).restoreKeychain({ ...opts, imported: true, diff --git a/src/core/keychain/keychainTypes/hardwareWalletKeychain.ts b/src/core/keychain/keychainTypes/hardwareWalletKeychain.ts index 06270ab0ca..2452c5eaa3 100644 --- a/src/core/keychain/keychainTypes/hardwareWalletKeychain.ts +++ b/src/core/keychain/keychainTypes/hardwareWalletKeychain.ts @@ -15,7 +15,7 @@ export interface SerializedHardwareWalletKeychain { hdPath?: string; deviceId?: string; accountsEnabled?: number; - type: string; + type: KeychainType.HardwareWalletKeychain; autodiscover?: boolean; wallets?: Array<{ address: Address; index: number; hdPath?: string }>; accountsDeleted?: Array; @@ -24,11 +24,11 @@ export interface SerializedHardwareWalletKeychain { const privates = new WeakMap(); export class HardwareWalletKeychain implements IKeychain { - type: string; + type: KeychainType.HardwareWalletKeychain = + KeychainType.HardwareWalletKeychain; vendor?: string; constructor() { - this.type = KeychainType.HardwareWalletKeychain; this.vendor = undefined; privates.set(this, { diff --git a/src/core/keychain/keychainTypes/hdKeychain.ts b/src/core/keychain/keychainTypes/hdKeychain.ts index 4e1671f0de..aba32314c7 100644 --- a/src/core/keychain/keychainTypes/hdKeychain.ts +++ b/src/core/keychain/keychainTypes/hdKeychain.ts @@ -26,7 +26,7 @@ export interface SerializedHdKeychain { mnemonic: string; hdPath?: SupportedHDPath; accountsEnabled?: number; - type: string; + type: KeychainType.HdKeychain; imported?: boolean; autodiscover?: boolean; accountsDeleted?: Array; @@ -47,11 +47,10 @@ const privates = new WeakMap< >(); export class HdKeychain implements IKeychain { - type: string; + type: KeychainType.HdKeychain = KeychainType.HdKeychain; imported: boolean; constructor() { - this.type = KeychainType.HdKeychain; this.imported = false; privates.set(this, { diff --git a/src/core/keychain/keychainTypes/keyPairKeychain.ts b/src/core/keychain/keychainTypes/keyPairKeychain.ts index 2470843139..cebbcc5392 100644 --- a/src/core/keychain/keychainTypes/keyPairKeychain.ts +++ b/src/core/keychain/keychainTypes/keyPairKeychain.ts @@ -11,17 +11,16 @@ import { IKeychain, PrivateKey, TWallet } from '../IKeychain'; import { RainbowSigner } from '../RainbowSigner'; export interface SerializedKeypairKeychain { - type: string; + type: KeychainType.KeyPairKeychain; privateKey: PrivateKey; } const privates = new WeakMap(); export class KeyPairKeychain implements IKeychain { - type: string; + type: KeychainType.KeyPairKeychain = KeychainType.KeyPairKeychain; constructor() { - this.type = KeychainType.KeyPairKeychain; privates.set(this, { wallets: [], }); diff --git a/src/core/keychain/keychainTypes/readOnlyKeychain.ts b/src/core/keychain/keychainTypes/readOnlyKeychain.ts index 165eae8d46..75542f32d9 100644 --- a/src/core/keychain/keychainTypes/readOnlyKeychain.ts +++ b/src/core/keychain/keychainTypes/readOnlyKeychain.ts @@ -11,17 +11,13 @@ import { logger } from '~/logger'; import { IKeychain, PrivateKey } from '../IKeychain'; export interface SerializedReadOnlyKeychain { - type: string; + type: KeychainType.ReadOnlyKeychain; address: Address; } export class ReadOnlyKeychain implements IKeychain { - type: string; + type: KeychainType.ReadOnlyKeychain = KeychainType.ReadOnlyKeychain; address?: Address; - constructor() { - this.type = KeychainType.ReadOnlyKeychain; - } - init(options: SerializedReadOnlyKeychain) { this.deserialize(options); } From b9b1887d5bc158e4e6c69afc75b80c39a2a22c47 Mon Sep 17 00:00:00 2001 From: gregs Date: Wed, 27 Mar 2024 00:26:07 -0300 Subject: [PATCH 2/3] merge keychains --- src/core/keychain/KeychainManager.ts | 56 +++++++++++++------ src/core/keychain/index.ts | 17 ++++-- src/core/keychain/keychainTypes/hdKeychain.ts | 17 ++++-- src/core/types/walletActions.ts | 1 + .../background/handlers/handleWallets.ts | 4 ++ .../ImportWallet/ImportWalletSelection.tsx | 33 +++++------ .../ImportWalletSelectionEdit.tsx | 7 ++- .../ImportWallet/ImportWalletViaSeed.tsx | 24 +++++--- src/entries/popup/handlers/wallet.ts | 3 + static/json/languages/en_US.json | 3 +- 10 files changed, 115 insertions(+), 50 deletions(-) diff --git a/src/core/keychain/KeychainManager.ts b/src/core/keychain/KeychainManager.ts index 62a3a43811..9ffb5c8772 100644 --- a/src/core/keychain/KeychainManager.ts +++ b/src/core/keychain/KeychainManager.ts @@ -147,7 +147,7 @@ class KeychainManager { default: throw new Error('Keychain type not recognized.'); } - await this.overrideReadOnlyKeychains(keychain); + await this.mergeKeychains(keychain); await this.checkForDuplicateInKeychain(keychain); this.state.keychains.push(keychain as Keychain); return keychain; @@ -270,20 +270,27 @@ class KeychainManager { return false; } - async overrideReadOnlyKeychains(incomingKeychain: Keychain) { + async mergeKeychains(incomingKeychain: Keychain) { if (incomingKeychain.type === KeychainType.ReadOnlyKeychain) return; + const currentAccounts = await this.getAccounts(); const incomingAccounts = await incomingKeychain.getAccounts(); const conflictingAccounts = incomingAccounts.filter((acc) => currentAccounts.includes(acc), ); - await Promise.all( - conflictingAccounts.map(async (acc) => { - const wallet = await this.getWallet(acc); - const isReadOnly = wallet.type === KeychainType.ReadOnlyKeychain; - if (isReadOnly) this.removeAccount(acc); - }), - ); + + for (const account of conflictingAccounts) { + const wallet = await this.getWallet(account); + // the incoming is not readOnly, so if the conflicting is, remove it to leave the one with higher privilages + // if the incoming is a hd wallet that derives an account in which the pk is already in the vault, remove this pk to leave the hd as the main + if ( + wallet.type === KeychainType.ReadOnlyKeychain || + (incomingKeychain.type === KeychainType.HdKeychain && + wallet.type === KeychainType.KeyPairKeychain) + ) { + this.removeAccount(account); + } + } } async checkForDuplicateInKeychain(keychain: Keychain) { @@ -326,7 +333,31 @@ class KeychainManager { return keychain; } + async removeKeychain(keychain: Keychain) { + this.state.keychains = this.state.keychains.filter((k) => k !== keychain); + } + + async isMnemonicInVault(mnemonic: string) { + for (const k of this.state.keychains) { + if (k.type != KeychainType.HdKeychain) continue; + if ((await k.exportKeychain()) == mnemonic) return true; + } + return false; + } + async importKeychain(opts: SerializedKeychain): Promise { + if (opts.type === KeychainType.KeyPairKeychain) { + const newAccount = (await this.deriveAccounts(opts))[0]; + const existingAccounts = await this.getAccounts(); + if (existingAccounts.includes(newAccount)) { + const existingKeychain = await this.getKeychain(newAccount); + // if the account is already in the vault (like in a hd keychain), we don't want to import it again + // UNLESS it's a readOnlyKeychain, which we DO WANT to override it, importing the pk + if (existingKeychain.type != KeychainType.ReadOnlyKeychain) + return existingKeychain; + } + } + const result = await privates.get(this).restoreKeychain({ ...opts, imported: true, @@ -512,13 +543,6 @@ class KeychainManager { throw new Error('No keychain found for account'); } - isAccountInReadOnlyKeychain(address: Address): Keychain | undefined { - for (const keychain of this.state.keychains) { - if (keychain.type !== KeychainType.ReadOnlyKeychain) continue; - if ((keychain as ReadOnlyKeychain).address === address) return keychain; - } - } - async getSigner(address: Address) { const keychain = await this.getKeychain(address); return keychain.getSigner(address); diff --git a/src/core/keychain/index.ts b/src/core/keychain/index.ts index a9e2b3f3b2..cbafbc651c 100644 --- a/src/core/keychain/index.ts +++ b/src/core/keychain/index.ts @@ -31,6 +31,7 @@ import { import { addHexPrefix } from '../utils/hex'; import { keychainManager } from './KeychainManager'; +import { SerializedKeypairKeychain } from './keychainTypes/keyPairKeychain'; interface TypedDataTypes { EIP712Domain: MessageTypeProperty[]; @@ -99,6 +100,10 @@ export const createWallet = async (): Promise
=> { return accounts[0]; }; +export const isMnemonicInVault = async (mnemonic: EthereumWalletSeed) => { + return keychainManager.isMnemonicInVault(mnemonic); +}; + export const deriveAccountsFromSecret = async ( secret: EthereumWalletSeed, ): Promise => { @@ -168,12 +173,16 @@ export const importWallet = async ( return address; } case EthereumWalletType.privateKey: { - const keychain = await keychainManager.importKeychain({ + const opts: SerializedKeypairKeychain = { type: KeychainType.KeyPairKeychain, privateKey: secret, - }); - const address = (await keychain.getAccounts())[0]; - return address; + }; + const newAccount = (await keychainManager.deriveAccounts(opts))[0]; + + await keychainManager.importKeychain(opts); + // returning the derived address instead of the first from the keychain, + // because this pk could have been elevated to hd while importing + return newAccount; } case EthereumWalletType.readOnly: { const keychain = await keychainManager.importKeychain({ diff --git a/src/core/keychain/keychainTypes/hdKeychain.ts b/src/core/keychain/keychainTypes/hdKeychain.ts index aba32314c7..31200c79f2 100644 --- a/src/core/keychain/keychainTypes/hdKeychain.ts +++ b/src/core/keychain/keychainTypes/hdKeychain.ts @@ -85,10 +85,19 @@ export class HdKeychain implements IKeychain { const _privates = privates.get(this)!; const derivedWallet = _privates.deriveWallet(index); - // if account already exists in a readonly keychain, remove it - keychainManager - .isAccountInReadOnlyKeychain(derivedWallet.address) - ?.removeAccount(derivedWallet.address); + // if account already exists in a another keychain, remove it + keychainManager.state.keychains.forEach(async (keychain) => { + const keychainAccounts = await keychain.getAccounts(); + if (keychainAccounts.includes(derivedWallet.address)) { + keychain.removeAccount(derivedWallet.address); + if ( + keychain.type == KeychainType.ReadOnlyKeychain || + keychain.type == KeychainType.KeyPairKeychain + ) { + keychainManager.removeKeychain(keychain); + } + } + }); const wallet = new Wallet( derivedWallet.privateKey as BytesLike, diff --git a/src/core/types/walletActions.ts b/src/core/types/walletActions.ts index 130e752dd0..501504b25e 100644 --- a/src/core/types/walletActions.ts +++ b/src/core/types/walletActions.ts @@ -7,6 +7,7 @@ export enum walletActions { unlock = 'unlock', verify_password = 'verify_password', derive_accounts_from_secret = 'derive_accounts_from_secret', + is_mnemonic_in_vault = 'is_mnemonic_in_vault', create = 'create', import = 'import', add = 'add', diff --git a/src/entries/background/handlers/handleWallets.ts b/src/entries/background/handlers/handleWallets.ts index 3947709e91..57c8d0c5e3 100644 --- a/src/entries/background/handlers/handleWallets.ts +++ b/src/entries/background/handlers/handleWallets.ts @@ -24,6 +24,7 @@ import { importHardwareWallet, importWallet, isInitialized, + isMnemonicInVault, isPasswordSet, isVaultUnlocked, lockVault, @@ -151,6 +152,9 @@ export const handleWallets = () => payload as EthereumWalletSeed, ); break; + case 'is_mnemonic_in_vault': + response = await isMnemonicInVault(payload as EthereumWalletSeed); + break; case 'get_accounts': response = await getAccounts(); break; diff --git a/src/entries/popup/components/ImportWallet/ImportWalletSelection.tsx b/src/entries/popup/components/ImportWallet/ImportWalletSelection.tsx index 344dd5e7bd..d107f9386a 100644 --- a/src/entries/popup/components/ImportWallet/ImportWalletSelection.tsx +++ b/src/entries/popup/components/ImportWallet/ImportWalletSelection.tsx @@ -1,3 +1,4 @@ +import { useMutation } from '@tanstack/react-query'; import { useEffect, useMemo, useState } from 'react'; import { Address } from 'wagmi'; @@ -69,17 +70,14 @@ const useDeriveAccountsFromSecrets = (secrets: string[]) => { }; export const useImportWalletsFromSecrets = () => { - const [isImporting, setIsImporting] = useState(false); - - const importSecrets = async ({ - secrets, - accountsIgnored = [], - }: { - secrets: string[]; - accountsIgnored?: Address[]; - }) => { - setIsImporting(true); - return (async () => { + const { mutateAsync: importSecrets, isLoading: isImporting } = useMutation({ + mutationFn: async ({ + secrets, + accountsIgnored = [], + }: { + secrets: string[]; + accountsIgnored?: Address[]; + }) => { const prevAccounts = await wallet.getAccounts(); await wallet.importWithSecret(secrets.join(' ')); @@ -94,12 +92,12 @@ export const useImportWalletsFromSecrets = () => { await Promise.all(accountsToRemove.map(wallet.remove)); return wallet.getAccounts(); - })().finally(() => { - setIsImporting(false); + }, + onSuccess: () => { derivedAccountsStore.clear(); removeImportWalletSecrets(); - }); - }; + }, + }); return { importSecrets, isImporting }; }; @@ -130,7 +128,10 @@ export const ImportWalletSelection = ({ onboarding = false }) => { const onImport = () => importSecrets({ secrets }).then(() => { setCurrentAddress(accountsToImport[0]); - if (onboarding) navigate(ROUTES.CREATE_PASSWORD); + if (onboarding) + navigate(ROUTES.CREATE_PASSWORD, { + state: { backTo: ROUTES.IMPORT__SEED }, + }); else navigate(ROUTES.HOME); }); diff --git a/src/entries/popup/components/ImportWallet/ImportWalletSelectionEdit.tsx b/src/entries/popup/components/ImportWallet/ImportWalletSelectionEdit.tsx index 79a1375612..27bb42f6b1 100644 --- a/src/entries/popup/components/ImportWallet/ImportWalletSelectionEdit.tsx +++ b/src/entries/popup/components/ImportWallet/ImportWalletSelectionEdit.tsx @@ -89,8 +89,11 @@ export function ImportWalletSelectionEdit({ onboarding = false }) { (a) => !accountsIgnored.includes(a), ); setCurrentAddress(importedAccounts[0]); - if (onboarding) navigate(ROUTES.CREATE_PASSWORD); - else navigate(ROUTES.HOME); + if (onboarding) { + navigate(ROUTES.CREATE_PASSWORD, { + state: { backTo: ROUTES.IMPORT__SEED }, + }); + } else navigate(ROUTES.HOME); }); return ( diff --git a/src/entries/popup/components/ImportWallet/ImportWalletViaSeed.tsx b/src/entries/popup/components/ImportWallet/ImportWalletViaSeed.tsx index 05465c4604..09c09c68f0 100644 --- a/src/entries/popup/components/ImportWallet/ImportWalletViaSeed.tsx +++ b/src/entries/popup/components/ImportWallet/ImportWalletViaSeed.tsx @@ -25,6 +25,7 @@ import { Text, textStyles, } from '~/design-system'; +import { triggerAlert } from '~/design-system/components/Alert/Alert'; import { accentSelectionStyle } from '~/design-system/components/Input/Input.css'; import { transformScales, @@ -36,6 +37,7 @@ import { removeImportWalletSecrets, setImportWalletSecrets, } from '../../handlers/importWalletSecrets'; +import { isMnemonicInVault } from '../../handlers/wallet'; import { useRainbowNavigate } from '../../hooks/useRainbowNavigate'; import { ROUTES } from '../../urls'; @@ -166,6 +168,9 @@ const secretsReducer = ( return newSecrets; }; +const emptySecrets12 = Array.from({ length: 12 }).map(() => ''); +const emptySecrets24 = Array.from({ length: 24 }).map(() => ''); + const ImportWalletViaSeed = () => { const navigate = useRainbowNavigate(); const location = useLocation(); @@ -174,16 +179,13 @@ const ImportWalletViaSeed = () => { const [globalError, setGlobalError] = useState(false); const [invalidWords, setInvalidWords] = useState([]); const [visibleInput, setVisibleInput] = useState(null); - const [secrets, setSecrets] = useReducer( - secretsReducer, - Array.from({ length: 12 }).map(() => ''), - ); + const [secrets, setSecrets] = useReducer(secretsReducer, emptySecrets12); const toggleWordLength = useCallback(() => { if (secrets.length === 12) { - setSecrets(Array.from({ length: 24 }).map(() => '')); + setSecrets(emptySecrets12); } else { - setSecrets(Array.from({ length: 12 }).map(() => '')); + setSecrets(emptySecrets24); } setInvalidWords([]); setGlobalError(false); @@ -228,10 +230,18 @@ const ImportWalletViaSeed = () => { ); const handleImportWallet = useCallback(async () => { + if (await isMnemonicInVault(secrets.join(' '))) { + triggerAlert({ + text: i18n.t('import_wallet_via_seed.duplicate_seed'), + }); + setSecrets(emptySecrets12); + return; + } + return navigate( onboarding ? ROUTES.IMPORT__SELECT : ROUTES.NEW_IMPORT_WALLET_SELECTION, ); - }, [navigate, onboarding]); + }, [navigate, onboarding, secrets]); const handleKeyDown = useCallback( (e: KeyboardEvent) => { diff --git a/src/entries/popup/handlers/wallet.ts b/src/entries/popup/handlers/wallet.ts index 3fea15e25e..609b9d29b2 100644 --- a/src/entries/popup/handlers/wallet.ts +++ b/src/entries/popup/handlers/wallet.ts @@ -259,6 +259,9 @@ export const updatePassword = async (password: string, newPassword: string) => { export const deriveAccountsFromSecret = async (secret: string) => walletAction('derive_accounts_from_secret', secret); +export const isMnemonicInVault = async (secret: string) => + walletAction('is_mnemonic_in_vault', secret); + export const verifyPassword = async (password: string) => walletAction('verify_password', password); diff --git a/static/json/languages/en_US.json b/static/json/languages/en_US.json index d09382410c..0a4d82e21f 100644 --- a/static/json/languages/en_US.json +++ b/static/json/languages/en_US.json @@ -907,7 +907,8 @@ "import_wallet_group": "Import wallet group", "n_words_might_be_wrong": "%{n} words may be misspelled or incorrect.", "1_word_might_be_wrong": "1 word may be misspelled or incorrect.", - "couldnt_paste": "We couldn’t paste your Secret Recovery Phrase" + "couldnt_paste": "We couldn’t paste your Secret Recovery Phrase", + "duplicate_seed": "This seed is imported already" }, "import_wallet_via_private_key": { "title": "Import Your Wallet", From 87483677c8be54e7606100633503373d350710e9 Mon Sep 17 00:00:00 2001 From: gregs Date: Fri, 29 Mar 2024 20:50:50 -0300 Subject: [PATCH 3/3] a --- .../ImportWallet/ImportWalletSelection.tsx | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/entries/popup/components/ImportWallet/ImportWalletSelection.tsx b/src/entries/popup/components/ImportWallet/ImportWalletSelection.tsx index d107f9386a..1a5b43b8e8 100644 --- a/src/entries/popup/components/ImportWallet/ImportWalletSelection.tsx +++ b/src/entries/popup/components/ImportWallet/ImportWalletSelection.tsx @@ -1,4 +1,3 @@ -import { useMutation } from '@tanstack/react-query'; import { useEffect, useMemo, useState } from 'react'; import { Address } from 'wagmi'; @@ -70,14 +69,17 @@ const useDeriveAccountsFromSecrets = (secrets: string[]) => { }; export const useImportWalletsFromSecrets = () => { - const { mutateAsync: importSecrets, isLoading: isImporting } = useMutation({ - mutationFn: async ({ - secrets, - accountsIgnored = [], - }: { - secrets: string[]; - accountsIgnored?: Address[]; - }) => { + const [isImporting, setIsImporting] = useState(false); + + const importSecrets = async ({ + secrets, + accountsIgnored = [], + }: { + secrets: string[]; + accountsIgnored?: Address[]; + }) => { + setIsImporting(true); + return (async () => { const prevAccounts = await wallet.getAccounts(); await wallet.importWithSecret(secrets.join(' ')); @@ -92,12 +94,12 @@ export const useImportWalletsFromSecrets = () => { await Promise.all(accountsToRemove.map(wallet.remove)); return wallet.getAccounts(); - }, - onSuccess: () => { + })().finally(() => { + setIsImporting(false); derivedAccountsStore.clear(); removeImportWalletSecrets(); - }, - }); + }); + }; return { importSecrets, isImporting }; };