diff --git a/packages/accounts-controller/src/AccountsController.ts b/packages/accounts-controller/src/AccountsController.ts index 5f83a4b8f69..ff1c6c429bf 100644 --- a/packages/accounts-controller/src/AccountsController.ts +++ b/packages/accounts-controller/src/AccountsController.ts @@ -40,6 +40,7 @@ import type { WritableDraft } from 'immer/dist/internal.js'; import { cloneDeep } from 'lodash'; import type { MultichainNetworkControllerNetworkDidChangeEvent } from './types'; +import type { AccountsControllerStrictState } from './typing'; import type { HdSnapKeyringAccount } from './utils'; import { getEvmDerivationPathForIndex, @@ -479,14 +480,8 @@ export class AccountsController extends BaseController< }; this.#update((state) => { - // FIXME: Using the state as-is cause the following error: "Type instantiation is excessively - // deep and possibly infinite.ts(2589)" (https://github.com/MetaMask/utils/issues/168) - // Using a type-cast workaround this error and is slightly better than using a @ts-expect-error - // which sometimes fail when compiling locally. - (state as AccountsControllerState).internalAccounts.accounts[account.id] = - internalAccount; - (state as AccountsControllerState).internalAccounts.selectedAccount = - account.id; + state.internalAccounts.accounts[account.id] = internalAccount; + state.internalAccounts.selectedAccount = account.id; }); this.messagingSystem.publish( @@ -529,12 +524,7 @@ export class AccountsController extends BaseController< }; this.#update((state) => { - // FIXME: Using the state as-is cause the following error: "Type instantiation is excessively - // deep and possibly infinite.ts(2589)" (https://github.com/MetaMask/utils/issues/168) - // Using a type-cast workaround this error and is slightly better than using a @ts-expect-error - // which sometimes fail when compiling locally. - (state as AccountsControllerState).internalAccounts.accounts[accountId] = - internalAccount; + state.internalAccounts.accounts[accountId] = internalAccount; }); if (metadata.name) { @@ -615,9 +605,11 @@ export class AccountsController extends BaseController< */ loadBackup(backup: AccountsControllerState): void { if (backup.internalAccounts) { - this.update((currentState) => { - currentState.internalAccounts = backup.internalAccounts; - }); + this.update( + (currentState: WritableDraft) => { + currentState.internalAccounts = backup.internalAccounts; + }, + ); } } @@ -906,13 +898,15 @@ export class AccountsController extends BaseController< * * @param callback - Callback for updating state, passed a draft state object. */ - #update(callback: (state: WritableDraft) => void) { + #update( + callback: (state: WritableDraft) => void, + ) { // The currently selected account might get deleted during the update, so keep track // of it before doing any change. const previouslySelectedAccount = this.state.internalAccounts.selectedAccount; - this.update((state) => { + this.update((state: WritableDraft) => { callback(state); // If the account no longer exists (or none is selected), we need to re-select another one. diff --git a/packages/accounts-controller/src/typing.ts b/packages/accounts-controller/src/typing.ts new file mode 100644 index 00000000000..1160df39ab3 --- /dev/null +++ b/packages/accounts-controller/src/typing.ts @@ -0,0 +1,35 @@ +import type { KeyringAccountEntropyOptions } from '@metamask/keyring-api'; +import type { InternalAccount } from '@metamask/keyring-internal-api'; + +import type { AccountsControllerState } from './AccountsController'; + +/** + * Type constraint to ensure a type is compatible with {@link AccountsControllerState}. + * If the constraint is not matching, this type will resolve to `never` and thus, fails + * to compile. + */ +type IsAccountControllerState = Type; + +/** + * A type compatible with {@link InternalAccount} which removes any use of recursive-type. + */ +export type StrictInternalAccount = Omit & { + // Use stricter options, which are relying on `Json` (which sometimes + // cause compiler errors because of instanciation "too deep". + // In anyway, we should rarely have to use those "untyped" options. + options: { + entropy?: KeyringAccountEntropyOptions; + exportable?: boolean; + }; +}; + +/** + * A type compatible with {@link AccountControllerState} which can be used to + * avoid recursive-type issue with `internalAccounts`. + */ +export type AccountsControllerStrictState = IsAccountControllerState<{ + internalAccounts: { + accounts: Record; + selectedAccount: InternalAccount['id']; + }; +}>;