diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index d392fcd4263..68aa626983e 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -3629,6 +3629,7 @@ export class TransactionController extends BaseController< this.#multichainTrackingHelper.acquireNonceLockForChainIdKey({ chainId, }), + messenger: this.messagingSystem, publishTransaction: (_ethQuery, transactionMeta) => this.publishTransaction(_ethQuery, transactionMeta, { skipSubmitHistory: true, diff --git a/packages/transaction-controller/src/helpers/PendingTransactionTracker.test.ts b/packages/transaction-controller/src/helpers/PendingTransactionTracker.test.ts index 28f0e14cfcc..149a701a1c0 100644 --- a/packages/transaction-controller/src/helpers/PendingTransactionTracker.test.ts +++ b/packages/transaction-controller/src/helpers/PendingTransactionTracker.test.ts @@ -5,6 +5,7 @@ import { freeze } from 'immer'; import { PendingTransactionTracker } from './PendingTransactionTracker'; import { TransactionPoller } from './TransactionPoller'; +import type { TransactionControllerMessenger } from '../TransactionController'; import type { TransactionMeta } from '../types'; import { TransactionStatus } from '../types'; @@ -74,11 +75,25 @@ function createTransactionPollerMock(): jest.Mocked { } as unknown as jest.Mocked; } +/** + * Creates a mock messenger instance. + * + * @returns The mock messenger instance. + */ +function createMessengerMock(): jest.Mocked { + return { + call: jest.fn().mockReturnValue({ + remoteFeatureFlags: {}, + }), + } as unknown as jest.Mocked; +} + describe('PendingTransactionTracker', () => { const queryMock = jest.mocked(query); let blockTracker: jest.Mocked; let pendingTransactionTracker: PendingTransactionTracker; let transactionPoller: jest.Mocked; + let messenger: jest.Mocked; let options: jest.Mocked< ConstructorParameters[0] @@ -112,6 +127,7 @@ describe('PendingTransactionTracker', () => { beforeEach(() => { blockTracker = createBlockTrackerMock(); transactionPoller = createTransactionPollerMock(); + messenger = createMessengerMock(); jest.mocked(TransactionPoller).mockImplementation(() => transactionPoller); @@ -123,6 +139,7 @@ describe('PendingTransactionTracker', () => { getTransactions: jest.fn(), getGlobalLock: jest.fn(() => Promise.resolve(jest.fn())), publishTransaction: jest.fn(), + messenger, }; }); diff --git a/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts b/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts index aa8cdb2ec4e..b2fffc8147a 100644 --- a/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts +++ b/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts @@ -4,6 +4,7 @@ import type { BlockTracker, NetworkClientId, } from '@metamask/network-controller'; +import type { Hex } from '@metamask/utils'; // This package purposefully relies on Node's EventEmitter module. // eslint-disable-next-line import-x/no-nodejs-modules import EventEmitter from 'events'; @@ -11,6 +12,7 @@ import { cloneDeep, merge } from 'lodash'; import { TransactionPoller } from './TransactionPoller'; import { createModuleLogger, projectLogger } from '../logger'; +import type { TransactionControllerMessenger } from '../TransactionController'; import type { TransactionMeta, TransactionReceipt } from '../types'; import { TransactionStatus, TransactionType } from '../types'; @@ -101,15 +103,16 @@ export class PendingTransactionTracker { blockTracker, getChainId, getEthQuery, + getGlobalLock, getNetworkClientId, getTransactions, + hooks, isResubmitEnabled, - getGlobalLock, + messenger, publishTransaction, - hooks, }: { blockTracker: BlockTracker; - getChainId: () => string; + getChainId: () => Hex; getEthQuery: (networkClientId?: NetworkClientId) => EthQuery; getNetworkClientId: () => string; getTransactions: () => TransactionMeta[]; @@ -125,6 +128,7 @@ export class PendingTransactionTracker { ) => boolean; beforePublish?: (transactionMeta: TransactionMeta) => boolean; }; + messenger: TransactionControllerMessenger; }) { this.hub = new EventEmitter() as PendingTransactionTrackerEventEmitter; @@ -138,7 +142,11 @@ export class PendingTransactionTracker { this.#getGlobalLock = getGlobalLock; this.#publishTransaction = publishTransaction; this.#running = false; - this.#transactionPoller = new TransactionPoller(blockTracker); + this.#transactionPoller = new TransactionPoller({ + blockTracker, + chainId: getChainId(), + messenger, + }); this.#beforePublish = hooks?.beforePublish ?? (() => true); this.#beforeCheckPendingTransaction = hooks?.beforeCheckPendingTransaction ?? (() => true); diff --git a/packages/transaction-controller/src/helpers/TransactionPoller.test.ts b/packages/transaction-controller/src/helpers/TransactionPoller.test.ts index 26657bc6aa4..e7a85e6b5cb 100644 --- a/packages/transaction-controller/src/helpers/TransactionPoller.test.ts +++ b/packages/transaction-controller/src/helpers/TransactionPoller.test.ts @@ -1,12 +1,16 @@ import type { BlockTracker } from '@metamask/network-controller'; -import { ACCELERATED_COUNT_MAX, TransactionPoller } from './TransactionPoller'; +import { TransactionPoller } from './TransactionPoller'; import { flushPromises } from '../../../../tests/helpers'; +import type { TransactionControllerMessenger } from '../TransactionController'; import type { TransactionMeta } from '../types'; jest.useFakeTimers(); const BLOCK_NUMBER_MOCK = '0x123'; +const CHAIN_ID_MOCK = '0x1'; +const DEFAULT_ACCELERATED_COUNT_MAX = 10; +const DEFAULT_ACCELERATED_POLLING_INTERVAL_MS = 3000; const BLOCK_TRACKER_MOCK = { getLatestBlock: jest.fn(), @@ -14,6 +18,20 @@ const BLOCK_TRACKER_MOCK = { removeListener: jest.fn(), } as unknown as jest.Mocked; +const MESSENGER_MOCK = { + call: jest.fn().mockReturnValue({ + remoteFeatureFlags: {}, + }), +} as unknown as jest.Mocked; + +jest.mock('../utils/feature-flags', () => ({ + getAcceleratedPollingParams: () => ({ + countMax: DEFAULT_ACCELERATED_COUNT_MAX, + intervalMs: DEFAULT_ACCELERATED_POLLING_INTERVAL_MS, + }), + FEATURE_FLAG_TRANSACTIONS: 'confirmations_transactions', +})); + /** * Creates a mock transaction metadata object. * @@ -32,7 +50,11 @@ describe('TransactionPoller', () => { describe('Accelerated Polling', () => { it('invokes listener after timeout', async () => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); const listener = jest.fn(); poller.start(listener); @@ -46,21 +68,29 @@ describe('TransactionPoller', () => { }); it('stops creating timeouts after max reached', async () => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); const listener = jest.fn(); poller.start(listener); - for (let i = 0; i < ACCELERATED_COUNT_MAX * 3; i++) { + for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX * 3; i++) { jest.runOnlyPendingTimers(); await flushPromises(); } - expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX); + expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX); }); it('invokes listener with latest block number from block tracker', async () => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); BLOCK_TRACKER_MOCK.getLatestBlock.mockResolvedValue(BLOCK_NUMBER_MOCK); @@ -74,7 +104,11 @@ describe('TransactionPoller', () => { }); it('does not create timeout if stopped while listener being invoked', async () => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); const listener = jest.fn(); listener.mockImplementation(() => poller.stop()); @@ -90,12 +124,16 @@ describe('TransactionPoller', () => { describe('Block Tracker Polling', () => { it('invokes listener on block tracker update after accelerated limit reached', async () => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); const listener = jest.fn(); poller.start(listener); - for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) { + for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) { jest.runOnlyPendingTimers(); await flushPromises(); } @@ -106,16 +144,20 @@ describe('TransactionPoller', () => { BLOCK_TRACKER_MOCK.on.mock.calls[0][1](); await flushPromises(); - expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX + 2); + expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX + 2); }); it('invokes listener with latest block number from event', async () => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); const listener = jest.fn(); poller.start(listener); - for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) { + for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) { jest.runOnlyPendingTimers(); await flushPromises(); } @@ -129,7 +171,11 @@ describe('TransactionPoller', () => { describe('start', () => { it('does nothing if already started', () => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); poller.start(jest.fn()); poller.start(jest.fn()); @@ -140,7 +186,11 @@ describe('TransactionPoller', () => { describe('stop', () => { it('removes timeout', () => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); const listener = jest.fn(); poller.start(listener); @@ -151,12 +201,16 @@ describe('TransactionPoller', () => { }); it('removes block tracker listener', async () => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); const listener = jest.fn(); poller.start(listener); - for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) { + for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) { jest.runOnlyPendingTimers(); await flushPromises(); } @@ -164,11 +218,15 @@ describe('TransactionPoller', () => { poller.stop(); expect(BLOCK_TRACKER_MOCK.removeListener).toHaveBeenCalledTimes(1); - expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX); + expect(listener).toHaveBeenCalledTimes(DEFAULT_ACCELERATED_COUNT_MAX); }); it('does nothing if not started', async () => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); poller.stop(); @@ -191,7 +249,11 @@ describe('TransactionPoller', () => { ])( 'resets accelerated count if transaction IDs %s', async (_title, newPendingTransactions) => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); poller.setPendingTransactions([ createTransactionMetaMock('1'), @@ -208,12 +270,14 @@ describe('TransactionPoller', () => { poller.setPendingTransactions(newPendingTransactions); - for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) { + for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) { jest.runOnlyPendingTimers(); await flushPromises(); } - expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX + 3); + expect(listener).toHaveBeenCalledTimes( + DEFAULT_ACCELERATED_COUNT_MAX + 3, + ); }, ); @@ -230,7 +294,11 @@ describe('TransactionPoller', () => { ])( 'resets to accelerated polling if transaction IDs added', async (_title, newPendingTransactions) => { - const poller = new TransactionPoller(BLOCK_TRACKER_MOCK); + const poller = new TransactionPoller({ + blockTracker: BLOCK_TRACKER_MOCK, + messenger: MESSENGER_MOCK, + chainId: CHAIN_ID_MOCK, + }); poller.setPendingTransactions([ createTransactionMetaMock('1'), @@ -240,7 +308,7 @@ describe('TransactionPoller', () => { const listener = jest.fn(); poller.start(listener); - for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) { + for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) { jest.runOnlyPendingTimers(); await flushPromises(); } @@ -253,12 +321,14 @@ describe('TransactionPoller', () => { poller.setPendingTransactions(newPendingTransactions); - for (let i = 0; i < ACCELERATED_COUNT_MAX; i++) { + for (let i = 0; i < DEFAULT_ACCELERATED_COUNT_MAX; i++) { jest.runOnlyPendingTimers(); await flushPromises(); } - expect(listener).toHaveBeenCalledTimes(ACCELERATED_COUNT_MAX * 2 + 2); + expect(listener).toHaveBeenCalledTimes( + DEFAULT_ACCELERATED_COUNT_MAX * 2 + 2, + ); }, ); }); diff --git a/packages/transaction-controller/src/helpers/TransactionPoller.ts b/packages/transaction-controller/src/helpers/TransactionPoller.ts index d4d9048076c..a6f65f9b784 100644 --- a/packages/transaction-controller/src/helpers/TransactionPoller.ts +++ b/packages/transaction-controller/src/helpers/TransactionPoller.ts @@ -1,12 +1,11 @@ import type { BlockTracker } from '@metamask/network-controller'; -import { createModuleLogger } from '@metamask/utils'; +import { createModuleLogger, type Hex } from '@metamask/utils'; import { isEqual } from 'lodash'; import { projectLogger } from '../logger'; +import type { TransactionControllerMessenger } from '../TransactionController'; import type { TransactionMeta } from '../types'; - -export const ACCELERATED_COUNT_MAX = 10; -export const ACCELERATED_INTERVAL = 1000 * 3; // 3 Seconds +import { getAcceleratedPollingParams } from '../utils/feature-flags'; const log = createModuleLogger(projectLogger, 'transaction-poller'); @@ -20,6 +19,10 @@ export class TransactionPoller { readonly #blockTracker: BlockTracker; + readonly #chainId: Hex; + + readonly #messenger: TransactionControllerMessenger; + #blockTrackerListener?: (latestBlockNumber: string) => void; #listener?: (latestBlockNumber: string) => Promise; @@ -30,8 +33,18 @@ export class TransactionPoller { #timeout?: NodeJS.Timeout; - constructor(blockTracker: BlockTracker) { + constructor({ + blockTracker, + chainId, + messenger, + }: { + blockTracker: BlockTracker; + chainId: Hex; + messenger: TransactionControllerMessenger; + }) { this.#blockTracker = blockTracker; + this.#chainId = chainId; + this.#messenger = messenger; } /** @@ -112,7 +125,12 @@ export class TransactionPoller { return; } - if (this.#acceleratedCount >= ACCELERATED_COUNT_MAX) { + const { countMax, intervalMs } = getAcceleratedPollingParams( + this.#chainId, + this.#messenger, + ); + + if (this.#acceleratedCount >= countMax) { // eslint-disable-next-line @typescript-eslint/no-misused-promises this.#blockTrackerListener = (latestBlockNumber) => this.#interval(false, latestBlockNumber); @@ -130,7 +148,7 @@ export class TransactionPoller { this.#timeout = setTimeout(async () => { await this.#interval(true); this.#queue(); - }, ACCELERATED_INTERVAL); + }, intervalMs); } async #interval(isAccelerated: boolean, latestBlockNumber?: string) { diff --git a/packages/transaction-controller/src/utils/feature-flags.test.ts b/packages/transaction-controller/src/utils/feature-flags.test.ts index 7da2b7e43c3..0b888d6b940 100644 --- a/packages/transaction-controller/src/utils/feature-flags.test.ts +++ b/packages/transaction-controller/src/utils/feature-flags.test.ts @@ -6,6 +6,7 @@ import type { TransactionControllerFeatureFlags } from './feature-flags'; import { FEATURE_FLAG_EIP_7702, FEATURE_FLAG_TRANSACTIONS, + getAcceleratedPollingParams, getBatchSizeLimit, getEIP7702ContractAddresses, getEIP7702SupportedChains, @@ -284,4 +285,147 @@ describe('Feature Flags Utils', () => { expect(getBatchSizeLimit(controllerMessenger)).toBe(10); }); }); + + describe('getAcceleratedPollingParams', () => { + it('returns default values if no feature flags set', () => { + mockFeatureFlags({}); + + const params = getAcceleratedPollingParams( + CHAIN_ID_MOCK as Hex, + controllerMessenger, + ); + + expect(params).toStrictEqual({ + countMax: 10, + intervalMs: 3000, + }); + }); + + it('returns values from chain-specific config when available', () => { + mockFeatureFlags({ + [FEATURE_FLAG_TRANSACTIONS]: { + acceleratedPolling: { + perChainConfig: { + [CHAIN_ID_MOCK]: { + countMax: 5, + intervalMs: 2000, + }, + }, + }, + }, + }); + + const params = getAcceleratedPollingParams( + CHAIN_ID_MOCK as Hex, + controllerMessenger, + ); + + expect(params).toStrictEqual({ + countMax: 5, + intervalMs: 2000, + }); + }); + + it('returns default values from feature flag when no chain-specific config', () => { + mockFeatureFlags({ + [FEATURE_FLAG_TRANSACTIONS]: { + acceleratedPolling: { + defaultCountMax: 15, + defaultIntervalMs: 4000, + }, + }, + }); + + const params = getAcceleratedPollingParams( + CHAIN_ID_MOCK as Hex, + controllerMessenger, + ); + + expect(params).toStrictEqual({ + countMax: 15, + intervalMs: 4000, + }); + }); + + it('uses chain-specific over default values', () => { + mockFeatureFlags({ + [FEATURE_FLAG_TRANSACTIONS]: { + acceleratedPolling: { + defaultCountMax: 15, + defaultIntervalMs: 4000, + perChainConfig: { + [CHAIN_ID_MOCK]: { + countMax: 5, + intervalMs: 2000, + }, + }, + }, + }, + }); + + const params = getAcceleratedPollingParams( + CHAIN_ID_MOCK as Hex, + controllerMessenger, + ); + + expect(params).toStrictEqual({ + countMax: 5, + intervalMs: 2000, + }); + }); + + it('uses defaults if chain not found in perChainConfig', () => { + mockFeatureFlags({ + [FEATURE_FLAG_TRANSACTIONS]: { + acceleratedPolling: { + defaultCountMax: 15, + defaultIntervalMs: 4000, + perChainConfig: { + [CHAIN_ID_2_MOCK]: { + countMax: 5, + intervalMs: 2000, + }, + }, + }, + }, + }); + + const params = getAcceleratedPollingParams( + CHAIN_ID_MOCK as Hex, + controllerMessenger, + ); + + expect(params).toStrictEqual({ + countMax: 15, + intervalMs: 4000, + }); + }); + + it('merges partial chain-specific config with defaults', () => { + mockFeatureFlags({ + [FEATURE_FLAG_TRANSACTIONS]: { + acceleratedPolling: { + defaultCountMax: 15, + defaultIntervalMs: 4000, + perChainConfig: { + [CHAIN_ID_MOCK]: { + // Only specify countMax, intervalMs should use default + countMax: 5, + }, + }, + }, + }, + }); + + const params = getAcceleratedPollingParams( + CHAIN_ID_MOCK as Hex, + controllerMessenger, + ); + + expect(params).toStrictEqual({ + countMax: 5, + intervalMs: 4000, + }); + }); + }); }); diff --git a/packages/transaction-controller/src/utils/feature-flags.ts b/packages/transaction-controller/src/utils/feature-flags.ts index 759a19ca77f..9be6d52ec2d 100644 --- a/packages/transaction-controller/src/utils/feature-flags.ts +++ b/packages/transaction-controller/src/utils/feature-flags.ts @@ -8,6 +8,8 @@ export const FEATURE_FLAG_TRANSACTIONS = 'confirmations_transactions'; export const FEATURE_FLAG_EIP_7702 = 'confirmations_eip_7702'; const DEFAULT_BATCH_SIZE_LIMIT = 10; +const DEFAULT_ACCELERATED_POLLING_COUNT_MAX = 10; +const DEFAULT_ACCELERATED_POLLING_INTERVAL_MS = 3 * 1000; export type TransactionControllerFeatureFlags = { [FEATURE_FLAG_EIP_7702]?: { @@ -34,6 +36,33 @@ export type TransactionControllerFeatureFlags = { [FEATURE_FLAG_TRANSACTIONS]?: { /** Maximum number of transactions that can be in an external batch. */ batchSizeLimit?: number; + + acceleratedPolling?: { + /** + * Accelerated polling is used to speed up the polling process for + * transactions that are not yet confirmed. + */ + perChainConfig?: { + /** Accelerated polling parameters on a per-chain basis. */ + + [chainId: Hex]: { + /** + * Maximum number of polling requests that can be made in a row, before + * the normal polling resumes. + */ + countMax?: number; + + /** Interval between polling requests in milliseconds. */ + intervalMs?: number; + }; + }; + + /** Default `countMax` in case no chain-specific parameter is set. */ + defaultCountMax?: number; + + /** Default `intervalMs` in case no chain-specific parameter is set. */ + defaultIntervalMs?: number; + }; }; }; @@ -116,6 +145,35 @@ export function getBatchSizeLimit( ); } +/** + * Retrieves the accelerated polling parameters for a given chain ID. + * + * @param chainId - The chain ID. + * @param messenger - The controller messenger instance. + * @returns The accelerated polling parameters: `countMax` and `intervalMs`. + */ +export function getAcceleratedPollingParams( + chainId: Hex, + messenger: TransactionControllerMessenger, +): { countMax: number; intervalMs: number } { + const featureFlags = getFeatureFlags(messenger); + + const acceleratedPollingParams = + featureFlags?.[FEATURE_FLAG_TRANSACTIONS]?.acceleratedPolling; + + const countMax = + acceleratedPollingParams?.perChainConfig?.[chainId]?.countMax || + acceleratedPollingParams?.defaultCountMax || + DEFAULT_ACCELERATED_POLLING_COUNT_MAX; + + const intervalMs = + acceleratedPollingParams?.perChainConfig?.[chainId]?.intervalMs || + acceleratedPollingParams?.defaultIntervalMs || + DEFAULT_ACCELERATED_POLLING_INTERVAL_MS; + + return { countMax, intervalMs }; +} + /** * Retrieves the relevant feature flags from the remote feature flag controller. *