From 69456e53e921d491f985aefe09fb3692efe12f22 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Thu, 11 Dec 2025 15:20:37 +0100 Subject: [PATCH 1/2] feat(multichain-account-service): add discovery.enabled config --- .../src/MultichainAccountService.test.ts | 2 +- .../src/providers/BtcAccountProvider.test.ts | 27 +++++++++++++++- .../src/providers/BtcAccountProvider.ts | 31 ++++++++++++------- .../src/providers/EvmAccountProvider.test.ts | 26 ++++++++++++++++ .../src/providers/EvmAccountProvider.ts | 29 ++++++++++++----- .../src/providers/SnapAccountProvider.ts | 5 +++ .../src/providers/SolAccountProvider.test.ts | 27 +++++++++++++++- .../src/providers/SolAccountProvider.ts | 31 ++++++++++++------- .../src/providers/TrxAccountProvider.test.ts | 27 +++++++++++++++- .../src/providers/TrxAccountProvider.ts | 31 ++++++++++++------- 10 files changed, 188 insertions(+), 48 deletions(-) diff --git a/packages/multichain-account-service/src/MultichainAccountService.test.ts b/packages/multichain-account-service/src/MultichainAccountService.test.ts index b2f649e4e2f..26b356c1c57 100644 --- a/packages/multichain-account-service/src/MultichainAccountService.test.ts +++ b/packages/multichain-account-service/src/MultichainAccountService.test.ts @@ -259,7 +259,7 @@ describe('MultichainAccountService', () => { ); expect(mocks.SolAccountProvider.constructor).toHaveBeenCalledWith( messenger, - providerConfigs?.[SolAccountProvider.NAME], + providerConfigs?.[SOL_ACCOUNT_PROVIDER_NAME], expect.any(Function), // TraceCallback ); }); diff --git a/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts b/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts index 6cd384c242d..6425be5a907 100644 --- a/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts @@ -9,9 +9,11 @@ import type { import { AccountProviderWrapper } from './AccountProviderWrapper'; import { + BTC_ACCOUNT_PROVIDER_DEFAULT_CONFIG, BTC_ACCOUNT_PROVIDER_NAME, BtcAccountProvider, } from './BtcAccountProvider'; +import { SnapAccountProviderConfig } from './SnapAccountProvider'; import { TraceName } from '../constants/traces'; import { getMultichainAccountServiceMessenger, @@ -103,14 +105,17 @@ class MockBtcKeyring { * @param options - Configuration options for setup. * @param options.messenger - An optional messenger instance to use. Defaults to a new Messenger. * @param options.accounts - List of accounts to use. + * @param options.config - Provider config. * @returns An object containing the controller instance and the messenger. */ function setup({ messenger = getRootMessenger(), accounts = [], + config, }: { messenger?: RootMessenger; accounts?: InternalAccount[]; + config?: SnapAccountProviderConfig; } = {}): { provider: AccountProviderWrapper; messenger: RootMessenger; @@ -153,7 +158,7 @@ function setup({ const multichainMessenger = getMultichainAccountServiceMessenger(messenger); const provider = new AccountProviderWrapper( multichainMessenger, - new BtcAccountProvider(multichainMessenger), + new BtcAccountProvider(multichainMessenger, config), ); return { @@ -329,6 +334,26 @@ describe('BtcAccountProvider', () => { expect(discovered).toStrictEqual([]); }); + it('does not run discovery if disabled', async () => { + const { provider } = setup({ + accounts: [MOCK_BTC_P2WPKH_ACCOUNT_1], + config: { + ...BTC_ACCOUNT_PROVIDER_DEFAULT_CONFIG, + discovery: { + ...BTC_ACCOUNT_PROVIDER_DEFAULT_CONFIG.discovery, + enabled: false, + }, + }, + }); + + expect( + await provider.discoverAccounts({ + entropySource: MOCK_HD_KEYRING_1.metadata.id, + groupIndex: 0, + }), + ).toStrictEqual([]); + }); + describe('trace functionality', () => { it('calls trace callback during account discovery', async () => { const mockTrace = jest.fn().mockImplementation(async (request, fn) => { diff --git a/packages/multichain-account-service/src/providers/BtcAccountProvider.ts b/packages/multichain-account-service/src/providers/BtcAccountProvider.ts index b4700fa5ec2..7e9f059f205 100644 --- a/packages/multichain-account-service/src/providers/BtcAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/BtcAccountProvider.ts @@ -15,7 +15,20 @@ import type { MultichainAccountServiceMessenger } from '../types'; export type BtcAccountProviderConfig = SnapAccountProviderConfig; -export const BTC_ACCOUNT_PROVIDER_NAME = 'Bitcoin' as const; +export const BTC_ACCOUNT_PROVIDER_NAME = 'Bitcoin'; + +export const BTC_ACCOUNT_PROVIDER_DEFAULT_CONFIG: BtcAccountProviderConfig = { + maxConcurrency: 3, + createAccounts: { + timeoutMs: 3000, + }, + discovery: { + enabled: true, + timeoutMs: 2000, + maxAttempts: 3, + backOffMs: 1000, + }, +}; export class BtcAccountProvider extends SnapAccountProvider { static NAME = BTC_ACCOUNT_PROVIDER_NAME; @@ -24,17 +37,7 @@ export class BtcAccountProvider extends SnapAccountProvider { constructor( messenger: MultichainAccountServiceMessenger, - config: BtcAccountProviderConfig = { - maxConcurrency: 3, - createAccounts: { - timeoutMs: 3000, - }, - discovery: { - timeoutMs: 2000, - maxAttempts: 3, - backOffMs: 1000, - }, - }, + config: BtcAccountProviderConfig = BTC_ACCOUNT_PROVIDER_DEFAULT_CONFIG, trace: TraceCallback = traceFallback, ) { super(BtcAccountProvider.BTC_SNAP_ID, messenger, config, trace); @@ -91,6 +94,10 @@ export class BtcAccountProvider extends SnapAccountProvider { }, }, async () => { + if (!this.config.discovery.enabled) { + return []; + } + const discoveredAccounts = await withRetry( () => withTimeout( diff --git a/packages/multichain-account-service/src/providers/EvmAccountProvider.test.ts b/packages/multichain-account-service/src/providers/EvmAccountProvider.test.ts index ecde7dab9f5..7970edc52e1 100644 --- a/packages/multichain-account-service/src/providers/EvmAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/EvmAccountProvider.test.ts @@ -12,8 +12,10 @@ import type { Hex } from '@metamask/utils'; import { createBytes } from '@metamask/utils'; import { + EVM_ACCOUNT_PROVIDER_DEFAULT_CONFIG, EVM_ACCOUNT_PROVIDER_NAME, EvmAccountProvider, + EvmAccountProviderConfig, } from './EvmAccountProvider'; import { TimeoutError } from './utils'; import { TraceName } from '../constants/traces'; @@ -117,18 +119,21 @@ class MockEthKeyring implements EthKeyring { * @param options.accounts - List of accounts to use. * @param options.discovery - Discovery options. * @param options.discovery.transactionCount - Transaction count (use '0x0' to stop the discovery). + * @param options.config - Provider config. * @returns An object containing the controller instance and the messenger. */ function setup({ messenger = getRootMessenger(), accounts = [], discovery, + config, }: { messenger?: RootMessenger; accounts?: InternalAccount[]; discovery?: { transactionCount: string; }; + config?: EvmAccountProviderConfig; } = {}): { provider: EvmAccountProvider; messenger: RootMessenger; @@ -190,6 +195,7 @@ function setup({ const provider = new EvmAccountProvider( getMultichainAccountServiceMessenger(messenger), + config, ); return { @@ -547,6 +553,26 @@ describe('EvmAccountProvider', () => { expect(mockTrace).toHaveBeenCalledTimes(1); }); + it('does not run discovery if disabled', async () => { + const { provider } = setup({ + accounts: [MOCK_HD_ACCOUNT_1, MOCK_HD_ACCOUNT_2], + config: { + ...EVM_ACCOUNT_PROVIDER_DEFAULT_CONFIG, + discovery: { + ...EVM_ACCOUNT_PROVIDER_DEFAULT_CONFIG.discovery, + enabled: false, + }, + }, + }); + + expect( + await provider.discoverAccounts({ + entropySource: MOCK_HD_KEYRING_1.metadata.id, + groupIndex: 0, + }), + ).toStrictEqual([]); + }); + it('does nothing when re-syncing accounts', async () => { const { provider } = setup({ accounts: [], diff --git a/packages/multichain-account-service/src/providers/EvmAccountProvider.ts b/packages/multichain-account-service/src/providers/EvmAccountProvider.ts index eb3f2618680..85f20ddd523 100644 --- a/packages/multichain-account-service/src/providers/EvmAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/EvmAccountProvider.ts @@ -42,6 +42,7 @@ function assertInternalAccountExists( export type EvmAccountProviderConfig = { discovery: { + enabled?: boolean; maxAttempts: number; timeoutMs: number; backOffMs: number; @@ -50,6 +51,14 @@ export type EvmAccountProviderConfig = { export const EVM_ACCOUNT_PROVIDER_NAME = 'EVM'; +export const EVM_ACCOUNT_PROVIDER_DEFAULT_CONFIG = { + discovery: { + maxAttempts: 3, + timeoutMs: 500, + backOffMs: 500, + }, +}; + export class EvmAccountProvider extends BaseBip44AccountProvider { static NAME = EVM_ACCOUNT_PROVIDER_NAME; @@ -59,17 +68,17 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { constructor( messenger: MultichainAccountServiceMessenger, - config: EvmAccountProviderConfig = { - discovery: { - maxAttempts: 3, - timeoutMs: 500, - backOffMs: 500, - }, - }, + config: EvmAccountProviderConfig = EVM_ACCOUNT_PROVIDER_DEFAULT_CONFIG, trace?: TraceCallback, ) { super(messenger); - this.#config = config; + this.#config = { + ...config, + discovery: { + ...config.discovery, + enabled: config.discovery.enabled ?? true, + }, + }; this.#trace = trace ?? traceFallback; } @@ -242,6 +251,10 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { }, }, async () => { + if (!this.#config.discovery.enabled) { + return []; + } + const provider = this.getEvmProvider(); const { entropySource, groupIndex } = opts; diff --git a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts index 3e1efc548d0..8ecc04ca6b6 100644 --- a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts @@ -22,6 +22,7 @@ export type RestrictedSnapKeyringCreateAccount = ( export type SnapAccountProviderConfig = { maxConcurrency?: number; discovery: { + enabled?: boolean; maxAttempts: number; timeoutMs: number; backOffMs: number; @@ -57,6 +58,10 @@ export abstract class SnapAccountProvider extends BaseBip44AccountProvider { const maxConcurrency = config.maxConcurrency ?? Infinity; this.config = { ...config, + discovery: { + ...config.discovery, + enabled: config.discovery.enabled ?? true, + }, maxConcurrency, }; diff --git a/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts b/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts index 5afd072d5e7..8d218513630 100644 --- a/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts @@ -7,7 +7,9 @@ import type { } from '@metamask/keyring-internal-api'; import { AccountProviderWrapper } from './AccountProviderWrapper'; +import { SnapAccountProviderConfig } from './SnapAccountProvider'; import { + SOL_ACCOUNT_PROVIDER_DEFAULT_CONFIG, SOL_ACCOUNT_PROVIDER_NAME, SolAccountProvider, } from './SolAccountProvider'; @@ -86,14 +88,17 @@ class MockSolanaKeyring { * @param options - Configuration options for setup. * @param options.messenger - An optional messenger instance to use. Defaults to a new Messenger. * @param options.accounts - List of accounts to use. + * @param options.config - Provider config. * @returns An object containing the controller instance and the messenger. */ function setup({ messenger = getRootMessenger(), accounts = [], + config, }: { messenger?: RootMessenger; accounts?: InternalAccount[]; + config?: SnapAccountProviderConfig; } = {}): { provider: AccountProviderWrapper; messenger: RootMessenger; @@ -146,7 +151,7 @@ function setup({ const multichainMessenger = getMultichainAccountServiceMessenger(messenger); const provider = new AccountProviderWrapper( multichainMessenger, - new SolAccountProvider(multichainMessenger, undefined, mockTrace), + new SolAccountProvider(multichainMessenger, config, mockTrace), ); return { @@ -323,6 +328,26 @@ describe('SolAccountProvider', () => { expect(discovered).toStrictEqual([]); }); + it('does not run discovery if disabled', async () => { + const { provider } = setup({ + accounts: [MOCK_SOL_ACCOUNT_1], + config: { + ...SOL_ACCOUNT_PROVIDER_DEFAULT_CONFIG, + discovery: { + ...SOL_ACCOUNT_PROVIDER_DEFAULT_CONFIG.discovery, + enabled: false, + }, + }, + }); + + expect( + await provider.discoverAccounts({ + entropySource: MOCK_HD_KEYRING_1.metadata.id, + groupIndex: 0, + }), + ).toStrictEqual([]); + }); + describe('trace functionality', () => { it('calls trace callback during account discovery', async () => { const { messenger, mocks } = setup({ diff --git a/packages/multichain-account-service/src/providers/SolAccountProvider.ts b/packages/multichain-account-service/src/providers/SolAccountProvider.ts index a6ef998bdb4..0201cee6b86 100644 --- a/packages/multichain-account-service/src/providers/SolAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/SolAccountProvider.ts @@ -20,7 +20,20 @@ import type { MultichainAccountServiceMessenger } from '../types'; export type SolAccountProviderConfig = SnapAccountProviderConfig; -export const SOL_ACCOUNT_PROVIDER_NAME = 'Solana' as const; +export const SOL_ACCOUNT_PROVIDER_NAME = 'Solana'; + +export const SOL_ACCOUNT_PROVIDER_DEFAULT_CONFIG: SnapAccountProviderConfig = { + maxConcurrency: 3, + discovery: { + enabled: true, + timeoutMs: 2000, + maxAttempts: 3, + backOffMs: 1000, + }, + createAccounts: { + timeoutMs: 3000, + }, +}; export class SolAccountProvider extends SnapAccountProvider { static NAME = SOL_ACCOUNT_PROVIDER_NAME; @@ -29,17 +42,7 @@ export class SolAccountProvider extends SnapAccountProvider { constructor( messenger: MultichainAccountServiceMessenger, - config: SolAccountProviderConfig = { - maxConcurrency: 3, - discovery: { - timeoutMs: 2000, - maxAttempts: 3, - backOffMs: 1000, - }, - createAccounts: { - timeoutMs: 3000, - }, - }, + config: SolAccountProviderConfig = SOL_ACCOUNT_PROVIDER_DEFAULT_CONFIG, trace: TraceCallback = traceFallback, ) { super(SolAccountProvider.SOLANA_SNAP_ID, messenger, config, trace); @@ -117,6 +120,10 @@ export class SolAccountProvider extends SnapAccountProvider { }, }, async () => { + if (!this.config.discovery.enabled) { + return []; + } + const discoveredAccounts = await withRetry( () => withTimeout( diff --git a/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts b/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts index 26f4fd104ec..51406f0917a 100644 --- a/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts @@ -7,7 +7,9 @@ import type { } from '@metamask/keyring-internal-api'; import { AccountProviderWrapper } from './AccountProviderWrapper'; +import { SnapAccountProviderConfig } from './SnapAccountProvider'; import { + TRX_ACCOUNT_PROVIDER_DEFAULT_CONFIG, TRX_ACCOUNT_PROVIDER_NAME, TrxAccountProvider, } from './TrxAccountProvider'; @@ -75,14 +77,17 @@ class MockTronKeyring { * @param options - Configuration options for setup. * @param options.messenger - An optional messenger instance to use. Defaults to a new Messenger. * @param options.accounts - List of accounts to use. + * @param options.config - Provider config. * @returns An object containing the controller instance and the messenger. */ function setup({ messenger = getRootMessenger(), accounts = [], + config, }: { messenger?: RootMessenger; accounts?: InternalAccount[]; + config?: SnapAccountProviderConfig; } = {}): { provider: AccountProviderWrapper; messenger: RootMessenger; @@ -132,7 +137,7 @@ function setup({ const multichainMessenger = getMultichainAccountServiceMessenger(messenger); const provider = new AccountProviderWrapper( multichainMessenger, - new TrxAccountProvider(multichainMessenger), + new TrxAccountProvider(multichainMessenger, config), ); return { @@ -313,6 +318,26 @@ describe('TrxAccountProvider', () => { expect(discovered).toStrictEqual([]); }); + it('does not run discovery if disabled', async () => { + const { provider } = setup({ + accounts: [MOCK_TRX_ACCOUNT_1], + config: { + ...TRX_ACCOUNT_PROVIDER_DEFAULT_CONFIG, + discovery: { + ...TRX_ACCOUNT_PROVIDER_DEFAULT_CONFIG.discovery, + enabled: false, + }, + }, + }); + + expect( + await provider.discoverAccounts({ + entropySource: MOCK_HD_KEYRING_1.metadata.id, + groupIndex: 0, + }), + ).toStrictEqual([]); + }); + describe('trace functionality', () => { it('calls trace callback during account discovery', async () => { const mockTrace = jest.fn().mockImplementation(async (request, fn) => { diff --git a/packages/multichain-account-service/src/providers/TrxAccountProvider.ts b/packages/multichain-account-service/src/providers/TrxAccountProvider.ts index 256f24720b9..6541a3aeb72 100644 --- a/packages/multichain-account-service/src/providers/TrxAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/TrxAccountProvider.ts @@ -16,7 +16,20 @@ import type { MultichainAccountServiceMessenger } from '../types'; export type TrxAccountProviderConfig = SnapAccountProviderConfig; -export const TRX_ACCOUNT_PROVIDER_NAME = 'Tron' as const; +export const TRX_ACCOUNT_PROVIDER_NAME = 'Tron'; + +export const TRX_ACCOUNT_PROVIDER_DEFAULT_CONFIG: TrxAccountProviderConfig = { + maxConcurrency: 3, + discovery: { + enabled: true, + timeoutMs: 2000, + maxAttempts: 3, + backOffMs: 1000, + }, + createAccounts: { + timeoutMs: 3000, + }, +}; export class TrxAccountProvider extends SnapAccountProvider { static NAME = TRX_ACCOUNT_PROVIDER_NAME; @@ -25,17 +38,7 @@ export class TrxAccountProvider extends SnapAccountProvider { constructor( messenger: MultichainAccountServiceMessenger, - config: TrxAccountProviderConfig = { - maxConcurrency: 3, - discovery: { - timeoutMs: 2000, - maxAttempts: 3, - backOffMs: 1000, - }, - createAccounts: { - timeoutMs: 3000, - }, - }, + config: TrxAccountProviderConfig = TRX_ACCOUNT_PROVIDER_DEFAULT_CONFIG, trace: TraceCallback = traceFallback, ) { super(TrxAccountProvider.TRX_SNAP_ID, messenger, config, trace); @@ -92,6 +95,10 @@ export class TrxAccountProvider extends SnapAccountProvider { }, }, async () => { + if (!this.config.discovery.enabled) { + return []; + } + const discoveredAccounts = await withRetry( () => withTimeout( From ed1915b61c7865c50ec733e306fade9675d2f6a9 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Thu, 11 Dec 2025 15:23:41 +0100 Subject: [PATCH 2/2] chore: update changelog --- packages/multichain-account-service/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/multichain-account-service/CHANGELOG.md b/packages/multichain-account-service/CHANGELOG.md index 8d389eb11a5..9ce0f7255f9 100644 --- a/packages/multichain-account-service/CHANGELOG.md +++ b/packages/multichain-account-service/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `config.discovery.enabled` option for all account provider config objects ([#7447](https://github.com/MetaMask/core/pull/7447)) +- Add `{EVM,SOL,BTC,TRX}_ACCOUNT_PROVIDER_DEFAULT_CONFIG` ([#7447](https://github.com/MetaMask/core/pull/7447)) + ## [4.0.1] ### Changed