diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index 864f6045b340..059acd7191f4 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -116,6 +116,9 @@ export const SENTRY_BACKGROUND_STATE = { destTokenAddress: true, srcTokenAmount: true, }, + quotes: [], + quotesLastFetched: true, + quotesLoadingStatus: true, }, }, CronjobController: { diff --git a/app/scripts/controllers/bridge/bridge-controller.test.ts b/app/scripts/controllers/bridge/bridge-controller.test.ts index 86fa6b513dbd..35449cb40764 100644 --- a/app/scripts/controllers/bridge/bridge-controller.test.ts +++ b/app/scripts/controllers/bridge/bridge-controller.test.ts @@ -2,6 +2,10 @@ import nock from 'nock'; import { BRIDGE_API_BASE_URL } from '../../../../shared/constants/bridge'; import { CHAIN_IDS } from '../../../../shared/constants/network'; import { SWAPS_API_V2_BASE_URL } from '../../../../shared/constants/swaps'; +import { flushPromises } from '../../../../test/lib/timer-helpers'; +// TODO: Remove restricted import +// eslint-disable-next-line import/no-restricted-paths +import * as bridgeUtil from '../../../../ui/pages/bridge/bridge.util'; import BridgeController from './bridge-controller'; import { BridgeControllerMessenger } from './types'; import { DEFAULT_BRIDGE_CONTROLLER_STATE } from './constants'; @@ -26,6 +30,8 @@ describe('BridgeController', function () { beforeEach(() => { jest.clearAllMocks(); + jest.clearAllTimers(); + nock(BRIDGE_API_BASE_URL) .get('/getAllFeatureFlags') .reply(200, { @@ -78,10 +84,28 @@ describe('BridgeController', function () { }; expect(bridgeController.state).toStrictEqual(EMPTY_INIT_STATE); + const setIntervalLengthSpy = jest.spyOn( + bridgeController, + 'setIntervalLength', + ); + await bridgeController.setBridgeFeatureFlags(); expect(bridgeController.state.bridgeState.bridgeFeatureFlags).toStrictEqual( expectedFeatureFlagsResponse, ); + expect(setIntervalLengthSpy).toHaveBeenCalledTimes(1); + expect(setIntervalLengthSpy).toHaveBeenCalledWith(3); + + bridgeController.resetState(); + expect(bridgeController.state.bridgeState).toStrictEqual( + expect.objectContaining({ + bridgeFeatureFlags: expectedFeatureFlagsResponse, + quotes: DEFAULT_BRIDGE_CONTROLLER_STATE.quotes, + quotesLastFetched: DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLastFetched, + quotesLoadingStatus: + DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLoadingStatus, + }), + ); }); it('selectDestNetwork should set the bridge dest tokens and top assets', async function () { @@ -204,4 +228,168 @@ describe('BridgeController', function () { walletAddress: undefined, }); }); + + it('updateBridgeQuoteRequestParams should trigger quote polling if request is valid', async function () { + jest.useFakeTimers(); + const stopAllPollingSpy = jest.spyOn(bridgeController, 'stopAllPolling'); + const startPollingByNetworkClientIdSpy = jest.spyOn( + bridgeController, + 'startPollingByNetworkClientId', + ); + messengerMock.call.mockReturnValue({ address: '0x123' } as never); + + const fetchBridgeQuotesSpy = jest + .spyOn(bridgeUtil, 'fetchBridgeQuotes') + .mockImplementationOnce(async () => { + return await new Promise((resolve) => { + return setTimeout(() => { + resolve([1, 2, 3] as never); + }, 5000); + }); + }); + + fetchBridgeQuotesSpy.mockImplementationOnce(async () => { + return await new Promise((resolve) => { + return setTimeout(() => { + resolve([5, 6, 7] as never); + }, 10000); + }); + }); + + fetchBridgeQuotesSpy.mockImplementationOnce(async () => { + return await new Promise((_, reject) => { + return setTimeout(() => { + reject(new Error('Network error')); + }, 10000); + }); + }); + + const quoteParams = { + srcChainId: 1, + destChainId: 10, + srcTokenAddress: '0x0000000000000000000000000000000000000000', + destTokenAddress: '0x123', + srcTokenAmount: '1000000000000000000', + }; + const quoteRequest = { + ...quoteParams, + slippage: 0.5, + walletAddress: '0x123', + }; + bridgeController.updateBridgeQuoteRequestParams(quoteParams); + + expect(stopAllPollingSpy).toHaveBeenCalledTimes(1); + expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1); + expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledWith( + '1', + quoteRequest, + ); + + expect(bridgeController.state.bridgeState).toStrictEqual( + expect.objectContaining({ + quoteRequest: { ...quoteRequest, walletAddress: undefined }, + quotes: DEFAULT_BRIDGE_CONTROLLER_STATE.quotes, + quotesLastFetched: DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLastFetched, + quotesLoadingStatus: + DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLoadingStatus, + }), + ); + + // Loading state + jest.advanceTimersByTime(1000); + await flushPromises(); + expect(fetchBridgeQuotesSpy).toHaveBeenCalledTimes(1); + expect(fetchBridgeQuotesSpy).toHaveBeenCalledWith(quoteRequest); + + const firstFetchTime = + bridgeController.state.bridgeState.quotesLastFetched ?? 0; + expect(firstFetchTime).toBeGreaterThan(0); + expect(bridgeController.state.bridgeState).toEqual( + expect.objectContaining({ + quoteRequest: { ...quoteRequest, walletAddress: undefined }, + quotes: [], + quotesLoadingStatus: 0, + }), + ); + + // After first fetch + jest.advanceTimersByTime(10000); + await flushPromises(); + expect(bridgeController.state.bridgeState).toEqual( + expect.objectContaining({ + quoteRequest: { ...quoteRequest, walletAddress: undefined }, + quotes: [1, 2, 3], + quotesLoadingStatus: 1, + }), + ); + expect(bridgeController.state.bridgeState.quotesLastFetched).toStrictEqual( + firstFetchTime, + ); + + // After 2nd fetch + jest.advanceTimersByTime(50000); + await flushPromises(); + expect(fetchBridgeQuotesSpy).toHaveBeenCalledTimes(2); + expect(bridgeController.state.bridgeState).toEqual( + expect.objectContaining({ + quoteRequest: { ...quoteRequest, walletAddress: undefined }, + quotes: [5, 6, 7], + quotesLoadingStatus: 1, + }), + ); + const secondFetchTime = + bridgeController.state.bridgeState.quotesLastFetched; + expect(secondFetchTime).toBeGreaterThan(firstFetchTime); + + // After 3nd fetch throws an error + jest.advanceTimersByTime(50000); + await flushPromises(); + expect(fetchBridgeQuotesSpy).toHaveBeenCalledTimes(3); + expect(bridgeController.state.bridgeState).toEqual( + expect.objectContaining({ + quoteRequest: { ...quoteRequest, walletAddress: undefined }, + quotes: [5, 6, 7], + quotesLoadingStatus: 2, + }), + ); + expect(bridgeController.state.bridgeState.quotesLastFetched).toStrictEqual( + secondFetchTime, + ); + }); + + it('updateBridgeQuoteRequestParams should not trigger quote polling if request is invalid', function () { + const stopAllPollingSpy = jest.spyOn(bridgeController, 'stopAllPolling'); + const startPollingByNetworkClientIdSpy = jest.spyOn( + bridgeController, + 'startPollingByNetworkClientId', + ); + messengerMock.call.mockReturnValueOnce({ address: '0x123' } as never); + + bridgeController.updateBridgeQuoteRequestParams({ + srcChainId: 1, + destChainId: 10, + srcTokenAddress: '0x0000000000000000000000000000000000000000', + destTokenAddress: '0x123', + }); + + expect(stopAllPollingSpy).toHaveBeenCalledTimes(1); + expect(startPollingByNetworkClientIdSpy).not.toHaveBeenCalled(); + + expect(bridgeController.state.bridgeState).toStrictEqual( + expect.objectContaining({ + quoteRequest: { + srcChainId: 1, + slippage: 0.5, + srcTokenAddress: '0x0000000000000000000000000000000000000000', + walletAddress: undefined, + destChainId: 10, + destTokenAddress: '0x123', + }, + quotes: DEFAULT_BRIDGE_CONTROLLER_STATE.quotes, + quotesLastFetched: DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLastFetched, + quotesLoadingStatus: + DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLoadingStatus, + }), + ); + }); }); diff --git a/app/scripts/controllers/bridge/bridge-controller.ts b/app/scripts/controllers/bridge/bridge-controller.ts index 0129dab0fac2..1d20e6f404e4 100644 --- a/app/scripts/controllers/bridge/bridge-controller.ts +++ b/app/scripts/controllers/bridge/bridge-controller.ts @@ -1,7 +1,10 @@ -import { BaseController, StateMetadata } from '@metamask/base-controller'; +import { StateMetadata } from '@metamask/base-controller'; import { Hex } from '@metamask/utils'; +import { StaticIntervalPollingController } from '@metamask/polling-controller'; +import { NetworkClientId } from '@metamask/network-controller'; import { fetchBridgeFeatureFlags, + fetchBridgeQuotes, fetchBridgeTokens, // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths @@ -9,14 +12,24 @@ import { // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { fetchTopAssetsList } from '../../../../ui/pages/swaps/swaps.util'; +import { decimalToHex } from '../../../../shared/modules/conversion.utils'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { QuoteRequest } from '../../../../ui/pages/bridge/types'; +// TODO: Remove restricted import +// eslint-disable-next-line import/no-restricted-paths +import { isValidQuoteRequest } from '../../../../ui/pages/bridge/utils/quote'; import { BRIDGE_CONTROLLER_NAME, DEFAULT_BRIDGE_CONTROLLER_STATE, + REFRESH_INTERVAL_MS, + RequestStatus, } from './constants'; -import { BridgeControllerState, BridgeControllerMessenger } from './types'; +import { + BridgeControllerState, + BridgeControllerMessenger, + BridgeFeatureFlagsKey, +} from './types'; const metadata: StateMetadata<{ bridgeState: BridgeControllerState }> = { bridgeState: { @@ -25,7 +38,7 @@ const metadata: StateMetadata<{ bridgeState: BridgeControllerState }> = { }, }; -export default class BridgeController extends BaseController< +export default class BridgeController extends StaticIntervalPollingController< typeof BRIDGE_CONTROLLER_NAME, { bridgeState: BridgeControllerState }, BridgeControllerMessenger @@ -35,9 +48,13 @@ export default class BridgeController extends BaseController< name: BRIDGE_CONTROLLER_NAME, metadata, messenger, - state: { bridgeState: DEFAULT_BRIDGE_CONTROLLER_STATE }, + state: { + bridgeState: DEFAULT_BRIDGE_CONTROLLER_STATE, + }, }); + this.setIntervalLength(REFRESH_INTERVAL_MS); + this.messagingSystem.registerActionHandler( `${BRIDGE_CONTROLLER_NAME}:setBridgeFeatureFlags`, this.setBridgeFeatureFlags.bind(this), @@ -56,7 +73,15 @@ export default class BridgeController extends BaseController< ); } + _executePoll = async ( + _: NetworkClientId, + updatedQuoteRequest: QuoteRequest, + ) => { + await this.#fetchBridgeQuotes(updatedQuoteRequest); + }; + updateBridgeQuoteRequestParams = (paramsToUpdate: Partial) => { + this.stopAllPolling(); const { bridgeState } = this.state; const updatedQuoteRequest = { ...DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest, @@ -66,17 +91,31 @@ export default class BridgeController extends BaseController< this.update((_state) => { _state.bridgeState = { ...bridgeState, - quoteRequest: { - ...updatedQuoteRequest, - }, + quoteRequest: updatedQuoteRequest, + quotes: DEFAULT_BRIDGE_CONTROLLER_STATE.quotes, + quotesLastFetched: DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLastFetched, + quotesLoadingStatus: + DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLoadingStatus, }; }); + + if (isValidQuoteRequest(updatedQuoteRequest)) { + const walletAddress = this.#getSelectedAccount().address; + this.startPollingByNetworkClientId( + decimalToHex(updatedQuoteRequest.srcChainId), + { ...updatedQuoteRequest, walletAddress }, + ); + } }; resetState = () => { + this.stopAllPolling(); this.update((_state) => { _state.bridgeState = { + ..._state.bridgeState, ...DEFAULT_BRIDGE_CONTROLLER_STATE, + quotes: [], + bridgeFeatureFlags: _state.bridgeState.bridgeFeatureFlags, }; }); }; @@ -87,6 +126,9 @@ export default class BridgeController extends BaseController< this.update((_state) => { _state.bridgeState = { ...bridgeState, bridgeFeatureFlags }; }); + this.setIntervalLength( + bridgeFeatureFlags[BridgeFeatureFlagsKey.EXTENSION_CONFIG].refreshRate, + ); }; selectSrcNetwork = async (chainId: Hex) => { @@ -99,6 +141,36 @@ export default class BridgeController extends BaseController< await this.#setTokens(chainId, 'destTokens'); }; + #fetchBridgeQuotes = async (request: QuoteRequest) => { + const { bridgeState } = this.state; + this.update((_state) => { + _state.bridgeState = { + ...bridgeState, + quotesLastFetched: Date.now(), + quotesLoadingStatus: RequestStatus.LOADING, + }; + }); + + try { + const quotes = await fetchBridgeQuotes(request); + this.update((_state) => { + _state.bridgeState = { + ..._state.bridgeState, + quotes, + quotesLoadingStatus: RequestStatus.FETCHED, + }; + }); + } catch (error) { + console.log('Failed to fetch bridge quotes', error); + this.update((_state) => { + _state.bridgeState = { + ...bridgeState, + quotesLoadingStatus: RequestStatus.ERROR, + }; + }); + } + }; + #setTopAssets = async ( chainId: Hex, stateKey: 'srcTopAssets' | 'destTopAssets', @@ -117,4 +189,8 @@ export default class BridgeController extends BaseController< _state.bridgeState = { ...bridgeState, [stateKey]: tokens }; }); }; + + #getSelectedAccount() { + return this.messagingSystem.call('AccountsController:getSelectedAccount'); + } } diff --git a/app/scripts/controllers/bridge/constants.ts b/app/scripts/controllers/bridge/constants.ts index 9506a8cc5073..a4aa3264fdc8 100644 --- a/app/scripts/controllers/bridge/constants.ts +++ b/app/scripts/controllers/bridge/constants.ts @@ -6,6 +6,12 @@ export const REFRESH_INTERVAL_MS = 30 * 1000; const DEFAULT_MAX_REFRESH_COUNT = 5; const DEFAULT_SLIPPAGE = 0.5; +export enum RequestStatus { + LOADING, + FETCHED, + ERROR, +} + export const DEFAULT_BRIDGE_CONTROLLER_STATE: BridgeControllerState = { bridgeFeatureFlags: { [BridgeFeatureFlagsKey.EXTENSION_CONFIG]: { @@ -25,4 +31,7 @@ export const DEFAULT_BRIDGE_CONTROLLER_STATE: BridgeControllerState = { srcTokenAddress: zeroAddress(), slippage: DEFAULT_SLIPPAGE, }, + quotes: [], + quotesLastFetched: undefined, + quotesLoadingStatus: undefined, }; diff --git a/app/scripts/controllers/bridge/types.ts b/app/scripts/controllers/bridge/types.ts index 15257ff6ec4b..10c2d8646545 100644 --- a/app/scripts/controllers/bridge/types.ts +++ b/app/scripts/controllers/bridge/types.ts @@ -3,12 +3,13 @@ import { RestrictedControllerMessenger, } from '@metamask/base-controller'; import { Hex } from '@metamask/utils'; +import { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller'; import { SwapsTokenObject } from '../../../../shared/constants/swaps'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths -import { QuoteRequest } from '../../../../ui/pages/bridge/types'; +import { QuoteRequest, QuoteResponse } from '../../../../ui/pages/bridge/types'; import BridgeController from './bridge-controller'; -import { BRIDGE_CONTROLLER_NAME } from './constants'; +import { BRIDGE_CONTROLLER_NAME, RequestStatus } from './constants'; export enum BridgeFeatureFlagsKey { EXTENSION_CONFIG = 'extensionConfig', @@ -34,6 +35,9 @@ export type BridgeControllerState = { destTokens: Record; destTopAssets: { address: string }[]; quoteRequest: Partial; + quotes: QuoteResponse[]; + quotesLastFetched?: number; + quotesLoadingStatus?: RequestStatus; }; export enum BridgeUserAction { @@ -62,13 +66,16 @@ type BridgeControllerEvents = ControllerStateChangeEvent< BridgeControllerState >; +type AllowedActions = AccountsControllerGetSelectedAccountAction['type']; +type AllowedEvents = never; + /** * The messenger for the BridgeController. */ export type BridgeControllerMessenger = RestrictedControllerMessenger< typeof BRIDGE_CONTROLLER_NAME, - BridgeControllerActions, + BridgeControllerActions | AccountsControllerGetSelectedAccountAction, BridgeControllerEvents, - never, - never + AllowedActions, + AllowedEvents >; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e95f08ecd6d2..a20f959e9019 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2111,7 +2111,7 @@ export default class MetamaskController extends EventEmitter { const bridgeControllerMessenger = this.controllerMessenger.getRestricted({ name: BRIDGE_CONTROLLER_NAME, - allowedActions: [], + allowedActions: ['AccountsController:getSelectedAccount'], allowedEvents: [], }); this.bridgeController = new BridgeController({ diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 9ccb9b8f435c..beaaed62937e 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -1211,24 +1211,24 @@ "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/gas-fee-controller>@metamask/base-controller": { "globals": { - "clearTimeout": true, - "console.error": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "immer": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": { + "@metamask/gas-fee-controller>@metamask/polling-controller": { "globals": { + "clearTimeout": true, + "console.error": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/gas-fee-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/jazzicon": { @@ -1985,6 +1985,18 @@ "punycode": true } }, + "@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/post-message-stream": { "globals": { "MessageEvent.prototype": true, @@ -2325,9 +2337,9 @@ "@ethersproject/bytes": true, "@metamask/controller-utils": true, "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@metamask/smart-transactions-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, "@metamask/smart-transactions-controller>bignumber.js": true, "browserify>buffer": true, @@ -2373,6 +2385,18 @@ "crypto.getRandomValues": true } }, + "@metamask/smart-transactions-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/smart-transactions-controller>@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -2875,9 +2899,9 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/base-controller": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/user-operation-controller>@metamask/rpc-errors": true, "@metamask/user-operation-controller>@metamask/utils": true, "bn.js": true, @@ -2895,6 +2919,18 @@ "immer": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/user-operation-controller>@metamask/base-controller": true, + "uuid": true + } + }, "@metamask/user-operation-controller>@metamask/rpc-errors": { "packages": { "@metamask/rpc-errors>fast-safe-stringify": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 9ccb9b8f435c..beaaed62937e 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1211,24 +1211,24 @@ "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/gas-fee-controller>@metamask/base-controller": { "globals": { - "clearTimeout": true, - "console.error": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "immer": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": { + "@metamask/gas-fee-controller>@metamask/polling-controller": { "globals": { + "clearTimeout": true, + "console.error": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/gas-fee-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/jazzicon": { @@ -1985,6 +1985,18 @@ "punycode": true } }, + "@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/post-message-stream": { "globals": { "MessageEvent.prototype": true, @@ -2325,9 +2337,9 @@ "@ethersproject/bytes": true, "@metamask/controller-utils": true, "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@metamask/smart-transactions-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, "@metamask/smart-transactions-controller>bignumber.js": true, "browserify>buffer": true, @@ -2373,6 +2385,18 @@ "crypto.getRandomValues": true } }, + "@metamask/smart-transactions-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/smart-transactions-controller>@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -2875,9 +2899,9 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/base-controller": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/user-operation-controller>@metamask/rpc-errors": true, "@metamask/user-operation-controller>@metamask/utils": true, "bn.js": true, @@ -2895,6 +2919,18 @@ "immer": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/user-operation-controller>@metamask/base-controller": true, + "uuid": true + } + }, "@metamask/user-operation-controller>@metamask/rpc-errors": { "packages": { "@metamask/rpc-errors>fast-safe-stringify": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 9ccb9b8f435c..beaaed62937e 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1211,24 +1211,24 @@ "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/gas-fee-controller>@metamask/base-controller": { "globals": { - "clearTimeout": true, - "console.error": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "immer": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": { + "@metamask/gas-fee-controller>@metamask/polling-controller": { "globals": { + "clearTimeout": true, + "console.error": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/gas-fee-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/jazzicon": { @@ -1985,6 +1985,18 @@ "punycode": true } }, + "@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/post-message-stream": { "globals": { "MessageEvent.prototype": true, @@ -2325,9 +2337,9 @@ "@ethersproject/bytes": true, "@metamask/controller-utils": true, "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@metamask/smart-transactions-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, "@metamask/smart-transactions-controller>bignumber.js": true, "browserify>buffer": true, @@ -2373,6 +2385,18 @@ "crypto.getRandomValues": true } }, + "@metamask/smart-transactions-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/smart-transactions-controller>@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -2875,9 +2899,9 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/base-controller": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/user-operation-controller>@metamask/rpc-errors": true, "@metamask/user-operation-controller>@metamask/utils": true, "bn.js": true, @@ -2895,6 +2919,18 @@ "immer": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/user-operation-controller>@metamask/base-controller": true, + "uuid": true + } + }, "@metamask/user-operation-controller>@metamask/rpc-errors": { "packages": { "@metamask/rpc-errors>fast-safe-stringify": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 502533c22dfe..b1184108c475 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -1303,24 +1303,24 @@ "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/gas-fee-controller>@metamask/base-controller": { "globals": { - "clearTimeout": true, - "console.error": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "immer": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": { + "@metamask/gas-fee-controller>@metamask/polling-controller": { "globals": { + "clearTimeout": true, + "console.error": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/gas-fee-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/jazzicon": { @@ -2077,6 +2077,18 @@ "punycode": true } }, + "@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/post-message-stream": { "globals": { "MessageEvent.prototype": true, @@ -2417,9 +2429,9 @@ "@ethersproject/bytes": true, "@metamask/controller-utils": true, "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@metamask/smart-transactions-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, "@metamask/smart-transactions-controller>bignumber.js": true, "browserify>buffer": true, @@ -2465,6 +2477,18 @@ "crypto.getRandomValues": true } }, + "@metamask/smart-transactions-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/smart-transactions-controller>@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -2967,9 +2991,9 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/base-controller": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/user-operation-controller>@metamask/rpc-errors": true, "@metamask/user-operation-controller>@metamask/utils": true, "bn.js": true, @@ -2987,6 +3011,18 @@ "immer": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/user-operation-controller>@metamask/base-controller": true, + "uuid": true + } + }, "@metamask/user-operation-controller>@metamask/rpc-errors": { "packages": { "@metamask/rpc-errors>fast-safe-stringify": true, diff --git a/package.json b/package.json index b97a1bdf5a9a..dcde3d41ff85 100644 --- a/package.json +++ b/package.json @@ -327,6 +327,7 @@ "@metamask/permission-controller": "^10.0.0", "@metamask/permission-log-controller": "^2.0.1", "@metamask/phishing-controller": "^12.3.0", + "@metamask/polling-controller": "^10.0.1", "@metamask/post-message-stream": "^8.0.0", "@metamask/ppom-validator": "0.35.1", "@metamask/preinstalled-example-snap": "^0.2.0", diff --git a/test/e2e/tests/metrics/errors.spec.js b/test/e2e/tests/metrics/errors.spec.js index 142810394c28..2cfee5240b99 100644 --- a/test/e2e/tests/metrics/errors.spec.js +++ b/test/e2e/tests/metrics/errors.spec.js @@ -873,6 +873,8 @@ describe('Sentry errors', function () { srcTokenAmount: true, walletAddress: false, }, + quotesLastFetched: true, + quotesLoadingStatus: true, }, currentPopupId: false, // Initialized as undefined // Part of transaction controller store, but missing from the initial diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index d95e3dd4313a..80ea24c8cc3a 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -73,6 +73,7 @@ "slippage": 0.5, "srcTokenAddress": "0x0000000000000000000000000000000000000000" }, + "quotes": {}, "srcTokens": {}, "srcTopAssets": {} } diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index d7167a2849ec..6574204ec2bf 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -268,6 +268,7 @@ "slippage": 0.5, "srcTokenAddress": "0x0000000000000000000000000000000000000000" }, + "quotes": {}, "srcTokens": {}, "srcTopAssets": {} }, diff --git a/yarn.lock b/yarn.lock index a8024a521207..bcef45870753 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5876,6 +5876,22 @@ __metadata: languageName: node linkType: hard +"@metamask/polling-controller@npm:^10.0.1": + version: 10.0.1 + resolution: "@metamask/polling-controller@npm:10.0.1" + dependencies: + "@metamask/base-controller": "npm:^7.0.1" + "@metamask/controller-utils": "npm:^11.3.0" + "@metamask/utils": "npm:^9.1.0" + "@types/uuid": "npm:^8.3.0" + fast-json-stable-stringify: "npm:^2.1.0" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/network-controller": ^21.0.0 + checksum: 10/25c11e65eeccb08a2b4b7dec21ccabb4b797907edb03a1534ebacb87d0754a3ade52aad061aad8b3ac23bfc39917c0d61b9734e32bc748c210b2997410ae45a9 + languageName: node + linkType: hard + "@metamask/polling-controller@npm:^11.0.0": version: 11.0.0 resolution: "@metamask/polling-controller@npm:11.0.0" @@ -25937,6 +25953,7 @@ __metadata: "@metamask/permission-log-controller": "npm:^2.0.1" "@metamask/phishing-controller": "npm:^12.3.0" "@metamask/phishing-warning": "npm:^4.1.0" + "@metamask/polling-controller": "npm:^10.0.1" "@metamask/post-message-stream": "npm:^8.0.0" "@metamask/ppom-validator": "npm:0.35.1" "@metamask/preferences-controller": "npm:^13.0.2"