diff --git a/packages/multichain-account-service/CHANGELOG.md b/packages/multichain-account-service/CHANGELOG.md index 428e0be92f7..de9b642ea9c 100644 --- a/packages/multichain-account-service/CHANGELOG.md +++ b/packages/multichain-account-service/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **BREAKING:** Replace `KeyringController:withKeyring` with `KeyringController:withKeyringV2` across all account providers ([#8491](https://github.com/MetaMask/core/pull/8491)) - Bump `@metamask/accounts-controller` from `^37.1.1` to `^37.2.0` ([#8363](https://github.com/MetaMask/core/pull/8363)) - Bump `@metamask/keyring-controller` from `^25.1.1` to `^25.2.0` ([#8363](https://github.com/MetaMask/core/pull/8363)) - Bump `@metamask/messenger` from `^1.0.0` to `^1.1.1` ([#8364](https://github.com/MetaMask/core/pull/8364), [#8373](https://github.com/MetaMask/core/pull/8373)) diff --git a/packages/multichain-account-service/src/MultichainAccountService.test.ts b/packages/multichain-account-service/src/MultichainAccountService.test.ts index d8b3456d113..3b6dee0f44a 100644 --- a/packages/multichain-account-service/src/MultichainAccountService.test.ts +++ b/packages/multichain-account-service/src/MultichainAccountService.test.ts @@ -1537,7 +1537,7 @@ describe('MultichainAccountService', () => { ); rootMessenger.registerActionHandler( - 'KeyringController:withKeyring', + 'KeyringController:withKeyringV2', async (_, operation) => { const newKeyring = mocks.KeyringController.keyrings.find( (keyring) => keyring.type === 'HD Key Tree', @@ -1580,7 +1580,7 @@ describe('MultichainAccountService', () => { ); rootMessenger.registerActionHandler( - 'KeyringController:withKeyring', + 'KeyringController:withKeyringV2', async (_, operation) => { const newKeyring = mocks.KeyringController.keyrings.find( (keyring) => keyring.type === 'HD Key Tree', diff --git a/packages/multichain-account-service/src/providers/BaseBip44AccountProvider.ts b/packages/multichain-account-service/src/providers/BaseBip44AccountProvider.ts index 9f27f2fc72a..4397c805856 100644 --- a/packages/multichain-account-service/src/providers/BaseBip44AccountProvider.ts +++ b/packages/multichain-account-service/src/providers/BaseBip44AccountProvider.ts @@ -8,7 +8,7 @@ import type { import type { KeyringCapabilities } from '@metamask/keyring-api/v2'; import type { KeyringMetadata, - KeyringSelector, + KeyringSelectorV2, } from '@metamask/keyring-controller'; import type { InternalAccount } from '@metamask/keyring-internal-api'; @@ -156,7 +156,7 @@ export abstract class BaseBip44AccountProvider< } protected async withKeyring( - selector: KeyringSelector, + selector: KeyringSelectorV2, operation: ({ keyring, metadata, @@ -166,7 +166,7 @@ export abstract class BaseBip44AccountProvider< }) => Promise, ): Promise { const result = await this.messenger.call( - 'KeyringController:withKeyring', + 'KeyringController:withKeyringV2', selector, ({ keyring, metadata }) => operation({ diff --git a/packages/multichain-account-service/src/providers/EvmAccountProvider.test.ts b/packages/multichain-account-service/src/providers/EvmAccountProvider.test.ts index ccd9cac13cb..58b6d524a38 100644 --- a/packages/multichain-account-service/src/providers/EvmAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/EvmAccountProvider.test.ts @@ -1,18 +1,15 @@ -import { publicToAddress } from '@ethereumjs/util'; import { isBip44Account } from '@metamask/account-api'; import { getUUIDFromAddressOfNormalAccount } from '@metamask/accounts-controller'; -import { AccountCreationType } from '@metamask/keyring-api'; +import { AccountCreationType, EthScope } from '@metamask/keyring-api'; +import type { KeyringAccount } from '@metamask/keyring-api'; +import type { Keyring } from '@metamask/keyring-api/v2'; +import { KeyringType } from '@metamask/keyring-api/v2'; import type { KeyringMetadata } from '@metamask/keyring-controller'; -import type { - EthKeyring, - InternalAccount, -} from '@metamask/keyring-internal-api'; +import type { InternalAccount } from '@metamask/keyring-internal-api'; import type { AutoManagedNetworkClient, CustomNetworkClientConfiguration, } from '@metamask/network-controller'; -import type { Hex } from '@metamask/utils'; -import { createBytes } from '@metamask/utils'; import { TraceName } from '../analytics/traces'; import { @@ -34,90 +31,87 @@ import { } from './EvmAccountProvider'; import { TimeoutError } from './utils'; -jest.mock('@ethereumjs/util', () => { - const actual = jest.requireActual('@ethereumjs/util'); +/** + * Converts an InternalAccount to a minimal KeyringAccount shape + * that satisfies what the controller accesses from the V2 keyring. + * + * @param account - The internal account to convert. + * @returns A KeyringAccount-shaped object. + */ +function toKeyringAccount(account: InternalAccount): KeyringAccount { return { - ...actual, - publicToAddress: jest.fn(), - }; -}); - -function mockNextDiscoveryAddress(address: string): void { - jest.mocked(publicToAddress).mockReturnValue(createBytes(address as Hex)); + id: account.id, + address: account.address, + type: account.type, + options: account.options, + methods: account.methods, + scopes: account.scopes ?? [], + } as unknown as KeyringAccount; } -function mockNextDiscoveryAddressOnce(address: string): void { - jest.mocked(publicToAddress).mockReturnValueOnce(createBytes(address as Hex)); -} - -type MockHdKey = { - deriveChild: jest.Mock; -}; +// Mock V2 HD Keyring implementing the Keyring interface from @metamask/keyring-api/v2. +class MockHdKeyringV2 implements Keyring { + readonly type = KeyringType.Hd; -function mockHdKey(): MockHdKey { - return { - deriveChild: jest.fn().mockImplementation(() => { - return { - publicKey: new Uint8Array(65), - }; - }), + readonly capabilities = { + scopes: [EthScope.Eoa], + bip44: { deriveIndex: true }, }; -} -class MockEthKeyring implements EthKeyring { - readonly type = 'MockEthKeyring'; + // Internal test-only state — not part of the Keyring interface. + readonly accounts: InternalAccount[]; readonly metadata: KeyringMetadata = { id: 'mock-eth-keyring-id', name: '', }; - readonly accounts: InternalAccount[]; - - readonly root: MockHdKey; - constructor(accounts: InternalAccount[]) { this.accounts = accounts; - this.root = mockHdKey(); - } - - async serialize(): Promise { - return 'serialized'; - } - - async deserialize(_: string): Promise { - // Not required. } getAccounts = jest .fn() - .mockImplementation(() => this.accounts.map((account) => account.address)); + .mockImplementation(() => this.accounts.map(toKeyringAccount)); + + getAccount = jest.fn().mockImplementation((accountId: string) => { + const account = this.accounts.find((a) => a.id === accountId); + if (!account) { + throw new Error(`Account not found: ${accountId}`); + } + return toKeyringAccount(account); + }); - addAccounts = jest.fn().mockImplementation((numberOfAccounts: number) => { - const newAccountsIndex = this.accounts.length; + createAccounts = jest + .fn() + .mockImplementation((options: Record) => { + const newAccounts: InternalAccount[] = []; - // Just generate a new address by appending the number of accounts owned by that fake keyring. - for (let i = 0; i < numberOfAccounts; i++) { - this.accounts.push( - MockAccountBuilder.from(MOCK_HD_ACCOUNT_1) + if (options.type === `${AccountCreationType.Bip44DeriveIndex}`) { + const account = MockAccountBuilder.from(MOCK_HD_ACCOUNT_1) .withUuid() .withAddressSuffix(`${this.accounts.length}`) .withGroupIndex(this.accounts.length) - .get(), - ); - } + .get(); + this.accounts.push(account); + newAccounts.push(account); + } - return this.accounts - .slice(newAccountsIndex) - .map((account) => account.address); - }); + return newAccounts.map(toKeyringAccount); + }); - removeAccount = jest.fn().mockImplementation((address: string) => { - const index = this.accounts.findIndex((a) => a.address === address); + deleteAccount = jest.fn().mockImplementation((accountId: string) => { + const index = this.accounts.findIndex((a) => a.id === accountId); if (index >= 0) { this.accounts.splice(index, 1); } }); + + serialize = jest.fn().mockResolvedValue({}); + + deserialize = jest.fn().mockResolvedValue(undefined); + + submitRequest = jest.fn(); } /** @@ -146,13 +140,13 @@ function setup({ } = {}): { provider: EvmAccountProvider; messenger: RootMessenger; - keyring: MockEthKeyring; + keyring: MockHdKeyringV2; mocks: { mockProviderRequest: jest.Mock; mockGetAccount: jest.Mock; }; } { - const keyring = new MockEthKeyring(accounts); + const keyring = new MockHdKeyringV2(accounts); messenger.registerActionHandler( 'AccountsController:getAccounts', @@ -196,7 +190,7 @@ function setup({ }); messenger.registerActionHandler( - 'KeyringController:withKeyring', + 'KeyringController:withKeyringV2', async (_, operation) => operation({ keyring, metadata: keyring.metadata }), ); @@ -218,8 +212,6 @@ function setup({ }, ); - mockNextDiscoveryAddress('0x123'); - const provider = new EvmAccountProvider( getMultichainAccountServiceMessenger(messenger), config, @@ -326,8 +318,9 @@ describe('EvmAccountProvider', () => { }); expect(newAccounts).toHaveLength(3); - expect(keyring.addAccounts).toHaveBeenCalledTimes(1); - expect(keyring.addAccounts).toHaveBeenCalledWith(3); + // HdKeyringV2 only supports bip44:derive-index, so range creation + // calls createAccounts once per new index. + expect(keyring.createAccounts).toHaveBeenCalledTimes(3); // Verify each account has the correct group index. for (const [index, account] of newAccounts.entries()) { @@ -351,8 +344,12 @@ describe('EvmAccountProvider', () => { }); expect(newAccounts).toHaveLength(3); - expect(keyring.addAccounts).toHaveBeenCalledTimes(1); - expect(keyring.addAccounts).toHaveBeenCalledWith(3); + expect(keyring.createAccounts).toHaveBeenCalledTimes(3); + expect(keyring.createAccounts).toHaveBeenCalledWith({ + type: AccountCreationType.Bip44DeriveIndex, + entropySource: MOCK_HD_KEYRING_1.metadata.id, + groupIndex: 0, + }); }); it('creates a single account when range from equals to', async () => { @@ -381,7 +378,8 @@ describe('EvmAccountProvider', () => { }); expect(newAccounts).toHaveLength(1); - expect(keyring.addAccounts).toHaveBeenCalledTimes(2); // 1 call for range 0-4, 1 call for account 5. + // 5 calls for range 0-4 + 1 call for account 5. + expect(keyring.createAccounts).toHaveBeenCalledTimes(6); expect( isBip44Account(newAccounts[0]) && newAccounts[0].options.entropy.groupIndex, @@ -429,9 +427,8 @@ describe('EvmAccountProvider', () => { expect(newAccounts).toHaveLength(4); expect(newAccounts[0]).toStrictEqual(MOCK_HD_ACCOUNT_1); expect(newAccounts[1]).toStrictEqual(MOCK_HD_ACCOUNT_2); - // Only 2 new accounts should be created (indices 2 and 3) in a single batched call. - expect(keyring.addAccounts).toHaveBeenCalledTimes(1); - expect(keyring.addAccounts).toHaveBeenCalledWith(2); + // Only new accounts (indices 2 and 3) should be created — one call each. + expect(keyring.createAccounts).toHaveBeenCalledTimes(2); }); it('throws if the created account is not BIP-44 compatible', async () => { @@ -516,8 +513,6 @@ describe('EvmAccountProvider', () => { id: expect.any(String), }; - mockNextDiscoveryAddressOnce(account.address); - expect( await provider.discoverAccounts({ entropySource: MOCK_HD_KEYRING_1.metadata.id, @@ -550,20 +545,14 @@ describe('EvmAccountProvider', () => { ); }); - it('stops discovery if there is no transaction activity', async () => { - const { provider } = setup({ + it('stops discovery if there is no transaction activity and deletes the created account', async () => { + const { provider, keyring } = setup({ accounts: [], discovery: { transactionCount: '0x0', }, }); - const account = MockAccountBuilder.from(MOCK_HD_ACCOUNT_1) - .withAddressSuffix('0') - .get(); - - mockNextDiscoveryAddressOnce(account.address); - expect( await provider.discoverAccounts({ entropySource: MOCK_HD_KEYRING_1.metadata.id, @@ -572,6 +561,9 @@ describe('EvmAccountProvider', () => { ).toStrictEqual([]); expect(provider.getAccounts()).toStrictEqual([]); + // The account was created to peek at the address, then deleted + // because there was no on-chain activity. + expect(keyring.deleteAccount).toHaveBeenCalledTimes(1); }); it('retries RPC request up to 3 times if it fails and throws the last error', async () => { @@ -657,8 +649,6 @@ describe('EvmAccountProvider', () => { id: expect.any(String), }; - mockNextDiscoveryAddressOnce(account.address); - // Create provider with custom trace callback const providerWithTrace = new EvmAccountProvider( getMultichainAccountServiceMessenger(messenger), @@ -695,8 +685,6 @@ describe('EvmAccountProvider', () => { id: expect.any(String), }; - mockNextDiscoveryAddressOnce(account.address); - const result = await provider.discoverAccounts({ entropySource: MOCK_HD_KEYRING_1.metadata.id, groupIndex: 0, @@ -721,12 +709,6 @@ describe('EvmAccountProvider', () => { }, }); - const account = MockAccountBuilder.from(MOCK_HD_ACCOUNT_1) - .withAddressSuffix('0') - .get(); - - mockNextDiscoveryAddressOnce(account.address); - const providerWithTrace = new EvmAccountProvider( getMultichainAccountServiceMessenger(messenger), { diff --git a/packages/multichain-account-service/src/providers/EvmAccountProvider.ts b/packages/multichain-account-service/src/providers/EvmAccountProvider.ts index 147fe6a768e..8c951677b36 100644 --- a/packages/multichain-account-service/src/providers/EvmAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/EvmAccountProvider.ts @@ -1,8 +1,6 @@ -import { publicToAddress } from '@ethereumjs/util'; import type { Bip44Account } from '@metamask/account-api'; import { getUUIDFromAddressOfNormalAccount } from '@metamask/accounts-controller'; import type { TraceCallback } from '@metamask/controller-utils'; -import type { HdKeyring } from '@metamask/eth-hd-keyring'; import type { CreateAccountOptions, EntropySourceId, @@ -14,16 +12,12 @@ import { EthAccountType, EthScope, } from '@metamask/keyring-api'; -import type { KeyringCapabilities } from '@metamask/keyring-api/v2'; +import type { KeyringCapabilities, Keyring } from '@metamask/keyring-api/v2'; import { KeyringTypes } from '@metamask/keyring-controller'; -import type { - EthKeyring, - InternalAccount, -} from '@metamask/keyring-internal-api'; +import type { InternalAccount } from '@metamask/keyring-internal-api'; import { AccountId } from '@metamask/keyring-utils'; import type { Provider } from '@metamask/network-controller'; -import { add0x, assert, bytesToHex, isStrictHexString } from '@metamask/utils'; -import type { Hex } from '@metamask/utils'; +import { isStrictHexString } from '@metamask/utils'; import { traceFallback } from '../analytics'; import { TraceName } from '../analytics/traces'; @@ -139,7 +133,7 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { * @param address - The address of the account. * @returns The account ID. */ - #getAccountId(address: Hex): string { + #getAccountId(address: string): string { return getUUIDFromAddressOfNormalAccount(address); } @@ -155,18 +149,18 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { async #createAccount({ entropySource, groupIndex, - throwOnGap = false, + throwOnGap, }: { entropySource: EntropySourceId; groupIndex: number; - throwOnGap?: boolean; - }): Promise<[Hex, boolean]> { - const result = await this.withKeyring( + throwOnGap: boolean; + }): Promise<[string, boolean]> { + const result = await this.withKeyring( { id: entropySource }, async ({ keyring }) => { const existing = await keyring.getAccounts(); if (groupIndex < existing.length) { - return [existing[groupIndex], false]; + return [existing[groupIndex].address, false]; } // If the throwOnGap flag is set, we throw an error to prevent index gaps. @@ -174,8 +168,12 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { throw new Error('Trying to create too many accounts'); } - const [added] = await keyring.addAccounts(1); - return [added, true]; + const [added] = await keyring.createAccounts({ + type: AccountCreationType.Bip44DeriveIndex, + entropySource, + groupIndex, + }); + return [added.address, true]; }, ); @@ -202,7 +200,7 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { const { range } = options; // Use a single withKeyring call for the entire range. - const accountIds = await this.withKeyring( + const accountIds = await this.withKeyring( { id: entropySource }, async ({ keyring }) => { const existing = await keyring.getAccounts(); @@ -224,21 +222,23 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { ) { if (groupIndex < existing.length) { // Account already exists. - result.push(this.#getAccountId(existing[groupIndex])); + result.push(this.#getAccountId(existing[groupIndex].address)); } } - // Determine if we need to create new accounts. - const from = Math.max(range.from, existing.length); - if (from <= range.to) { - // Calculate how many new accounts to create. - const accountsToCreate = range.to - existing.length + 1; - - // Create all new accounts in one call. - const newAccounts = await keyring.addAccounts(accountsToCreate); - result.push( - ...newAccounts.map((address) => this.#getAccountId(address)), - ); + // Create new accounts one-by-one since HdKeyringV2 only supports + // bip44:derive-index (not bip44:derive-index-range). + for ( + let groupIndex = Math.max(range.from, existing.length); + groupIndex <= range.to; + groupIndex++ + ) { + const [created] = await keyring.createAccounts({ + type: AccountCreationType.Bip44DeriveIndex, + entropySource, + groupIndex, + }); + result.push(this.#getAccountId(created.address)); } return result; @@ -295,7 +295,7 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { */ async #getTransactionCount( provider: Provider, - address: Hex, + address: string, ): Promise { const method = 'eth_getTransactionCount'; @@ -328,39 +328,14 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { return parseInt(response, 16); } - async #getAddressFromGroupIndex({ - entropySource, - groupIndex, - }: { - entropySource: EntropySourceId; - groupIndex: number; - }): Promise { - // NOTE: To avoid exposing this function at keyring level, we just re-use its internal state - // and compute the derivation here. - return await this.withKeyring( - { id: entropySource }, - async ({ keyring }) => { - // If the account already exist, do not re-derive and just re-use that account. - const existing = await keyring.getAccounts(); - if (groupIndex < existing.length) { - return existing[groupIndex]; - } - - // If not, then we just "peek" the next address to avoid creating the account. - assert(keyring.root, 'Expected HD keyring.root to be set'); - const hdKey = keyring.root.deriveChild(groupIndex); - assert(hdKey.publicKey, 'Expected public key to be set'); - - return add0x( - bytesToHex(publicToAddress(hdKey.publicKey, true)).toLowerCase(), - ); - }, - ); - } - /** * Discover and create accounts for the EVM provider. * + * Uses a single withKeyringV2 callback to create the account, check for + * on-chain activity, and delete it if inactive — all atomically. This + * ensures that when no activity is found, the net keyring state change is + * zero (no vault write, no stateChange event). + * * @param opts - The options for the discovery and creation of accounts. * @param opts.entropySource - The entropy source to use for the discovery and creation of accounts. * @param opts.groupIndex - The index of the group to create the accounts for. @@ -385,34 +360,59 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { const provider = this.getEvmProvider(); const { entropySource, groupIndex } = opts; - const addressFromGroupIndex = await this.#getAddressFromGroupIndex({ - entropySource, - groupIndex, - }); + // Everything happens inside a single withKeyringV2 callback so that + // a create+delete for inactive accounts results in zero net state + // change (no vault write, no events fired). + // + // The callback returns the address if activity was found, or null if + // not. The AccountsController lookup happens AFTER the callback + // completes, because newly created accounts are only visible to the + // AccountsController after withKeyringV2 persists and fires stateChange. + const discoveredAddress = await this.withKeyring< + Keyring, + string | null + >({ id: entropySource }, async ({ keyring }) => { + const existing = await keyring.getAccounts(); - const count = await this.#getTransactionCount( - provider, - addressFromGroupIndex, - ); - if (count === 0) { - return []; - } + let address: string; + let created: KeyringAccount | undefined; + + if (groupIndex < existing.length) { + // Account already exists — use its address. + address = existing[groupIndex].address; + } else { + // Account doesn't exist — create it to discover the address. + [created] = await keyring.createAccounts({ + type: AccountCreationType.Bip44DeriveIndex, + entropySource, + groupIndex, + }); + address = created.address; + } - // We have some activity on this address, we try to create the account. - const [address] = await this.#createAccount({ - entropySource, - groupIndex, + const count = await this.#getTransactionCount(provider, address); + if (count === 0) { + // No activity. If we created the account, undo it within this + // same callback so the net state change is zero. + if (created) { + await keyring.deleteAccount(created.id); + } + return null; + } + + // Activity found — account stays. Return the address so we can + // look it up in the AccountsController after the callback persists. + return address; }); - assert( - addressFromGroupIndex === address, - 'Created account does not match address from group index.', - ); - const accoundId = this.#getAccountId(address); + if (!discoveredAddress) { + return []; + } + const accountId = this.#getAccountId(discoveredAddress); const account = this.messenger.call( 'AccountsController:getAccount', - accoundId, + accountId, ); assertInternalAccountExists(account); assertIsBip44Account(account); diff --git a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts index eb0ce052c0f..723bb4f81c1 100644 --- a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts @@ -13,9 +13,16 @@ import type { EntropySourceId, KeyringAccount, } from '@metamask/keyring-api'; -import type { KeyringMetadata } from '@metamask/keyring-controller'; +import type { + KeyringMetadata, + KeyringSelector, + KeyringSelectorV2, +} from '@metamask/keyring-controller'; import { KeyringTypes } from '@metamask/keyring-controller'; -import type { InternalAccount } from '@metamask/keyring-internal-api'; +import type { + EthKeyring, + InternalAccount, +} from '@metamask/keyring-internal-api'; import { KeyringClient } from '@metamask/keyring-snap-client'; import type { Json, JsonRpcRequest, SnapId } from '@metamask/snaps-sdk'; import { HandlerType } from '@metamask/snaps-utils'; @@ -149,6 +156,38 @@ export abstract class SnapAccountProvider extends BaseBip44AccountProvider { return this.#trace(request, fn); } + // Override to keep snap providers on V1 withKeyring until the snap keyring + // migration is complete. The base class calls withKeyringV2 which the snap + // providers are not yet compatible with (they use V1 SnapKeyring methods). + protected override async withKeyring( + selector: KeyringSelectorV2, + operation: ({ + keyring, + metadata, + }: { + keyring: SelectedKeyring; + metadata: KeyringMetadata; + }) => Promise, + ): Promise { + const result = await this.messenger.call( + 'KeyringController:withKeyring', + selector as unknown as KeyringSelector, + ({ + keyring, + metadata: keyringMetadata, + }: { + keyring: EthKeyring; + metadata: KeyringMetadata; + }) => + operation({ + keyring: keyring as SelectedKeyring, + metadata: keyringMetadata, + }), + ); + + return result as CallbackResult; + } + async #getRestrictedSnapKeyring(): Promise { // NOTE: We're not supposed to make the keyring instance escape `withKeyring` but // we have to use the `SnapKeyring` instance to be able to create Solana account diff --git a/packages/multichain-account-service/src/tests/messenger.ts b/packages/multichain-account-service/src/tests/messenger.ts index e6f3136b5a4..8318ccaede7 100644 --- a/packages/multichain-account-service/src/tests/messenger.ts +++ b/packages/multichain-account-service/src/tests/messenger.ts @@ -65,6 +65,7 @@ export function getMultichainAccountServiceMessenger( 'SnapController:getState', 'SnapController:handleRequest', 'KeyringController:withKeyring', + 'KeyringController:withKeyringV2', 'KeyringController:getState', 'KeyringController:getKeyringsByType', 'KeyringController:addNewKeyring', diff --git a/packages/multichain-account-service/src/types.ts b/packages/multichain-account-service/src/types.ts index 8817722e102..6376af180a4 100644 --- a/packages/multichain-account-service/src/types.ts +++ b/packages/multichain-account-service/src/types.ts @@ -23,6 +23,7 @@ import type { KeyringControllerRemoveAccountAction, KeyringControllerStateChangeEvent, KeyringControllerWithKeyringAction, + KeyringControllerWithKeyringV2Action, } from '@metamask/keyring-controller'; import type { Messenger } from '@metamask/messenger'; import type { @@ -79,6 +80,7 @@ type AllowedActions = | AccountsControllerGetAccountAction | AccountsControllerGetAccountByAddressAction | KeyringControllerWithKeyringAction + | KeyringControllerWithKeyringV2Action | KeyringControllerGetStateAction | KeyringControllerGetKeyringsByTypeAction | KeyringControllerAddNewKeyringAction