Repository: MetaMask/metamask-mobile
Pull request #13750: feat: add bridge controllers to Engine
Author: login: amitabh94
Labels: name: team-swaps-and-bridge
description: Swaps and Bridge team
name: release-7.43.0
description: Issue or pull request that will be included in release 7.43.0
Created: 2025-02-27T17:03:40Z | Merged: 2025-03-05T15:15:32Z

<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once.
-->

## **Description**

This PR adds BridgeController and BridgeStatus Controllers to the Engine file and initializes them for use in the client codebase.
This also initializes the Bridge state which would be used by Bridge UI components.

This PR currently places the initialization of controllers directly int he Engine file rather than using the new modular structure as few things need to be ironed out with the modular approach regarding ordering of controllers and their dependencies.

This PR however creates new directories for the modular approach as well and will use them when the issue with modularization of controllers is fixed.

## **Related issues**

Fixes: [SWAPS-1981](https://consensyssoftware.atlassian.net/browse/MMS-1981)

## **Manual testing steps**

1. Launch the MetaMask Mobile app
2. Verify the app initializes without errors (check console for any Bridge Controller related errors)
3. Confirm the app state includes the new Bridge Controller entries:
   - BridgeController
   - BridgeStatusController

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable
- [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.



Diff:
diff --git a/app/core/Engine/Engine.test.ts b/app/core/Engine/Engine.test.ts
index 1b1c6e00789..734b30036c9 100644
--- a/app/core/Engine/Engine.test.ts
+++ b/app/core/Engine/Engine.test.ts
@@ -79,6 +79,8 @@ describe('Engine', () => {
     expect(engine.context).toHaveProperty('MultichainBalancesController');
     expect(engine.context).toHaveProperty('RatesController');
     expect(engine.context).toHaveProperty('MultichainNetworkController');
+    expect(engine.context).toHaveProperty('BridgeController');
+    expect(engine.context).toHaveProperty('BridgeStatusController');
   });
 
   it('calling Engine.init twice returns the same instance', () => {
diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts
index 54d511a2f08..7af69af2e51 100644
--- a/app/core/Engine/Engine.ts
+++ b/app/core/Engine/Engine.ts
@@ -39,6 +39,7 @@ import {
   TransactionMeta,
   TransactionControllerOptions,
   TransactionControllerMessenger,
+  type TransactionParams,
 } from '@metamask/transaction-controller';
 import { GasFeeController } from '@metamask/gas-fee-controller';
 import {
@@ -167,7 +168,11 @@ import {
 } from '@metamask/smart-transactions-controller/dist/types';
 import { submitSmartTransactionHook } from '../../util/smart-transactions/smart-publish-hook';
 import { zeroAddress } from 'ethereumjs-util';
-import { ApprovalType, toChecksumHexAddress } from '@metamask/controller-utils';
+import {
+  ApprovalType,
+  toChecksumHexAddress,
+  type ChainId,
+} from '@metamask/controller-utils';
 import { ExtendedControllerMessenger } from '../ExtendedControllerMessenger';
 import DomainProxyMap from '../../lib/DomainProxyMap/DomainProxyMap';
 import {
@@ -223,6 +228,8 @@ import {
   SnapControllerHandleRequestAction,
   SnapControllerUpdateSnapStateAction,
 } from './controllers/SnapController/constants';
+import { BridgeClientId, BridgeController } from '@metamask/bridge-controller';
+import { BridgeStatusController } from '@metamask/bridge-status-controller';
 import { multichainNetworkControllerInit } from './controllers/multichain-network-controller/multichain-network-controller-init';
 import { currencyRateControllerInit } from './controllers/currency-rate-controller/currency-rate-controller-init';
 
@@ -1280,6 +1287,52 @@ export class Engine {
       getMetaMetricsProps: () => Promise.resolve({}), // Return MetaMetrics props once we enable HW wallets for smart transactions.
     });
 
+    /* bridge controller Initialization */
+
+    const bridgeController = new BridgeController({
+      messenger: this.controllerMessenger.getRestricted({
+        name: 'BridgeController',
+        allowedActions: [
+          'AccountsController:getSelectedAccount',
+          'NetworkController:findNetworkClientIdByChainId',
+          'NetworkController:getState',
+          'NetworkController:getNetworkClientById',
+        ],
+        allowedEvents: [],
+      }),
+      clientId: BridgeClientId.MOBILE,
+      // TODO: change getLayer1GasFee type to match transactionController.getLayer1GasFee
+      getLayer1GasFee: async ({
+        transactionParams,
+        chainId,
+      }: {
+        transactionParams: TransactionParams;
+        chainId: ChainId;
+      }) =>
+        this.transactionController.getLayer1GasFee({
+          transactionParams,
+          chainId,
+          // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        }) as any,
+      fetchFn: fetch,
+    });
+
+    const bridgeStatusController = new BridgeStatusController({
+      messenger: this.controllerMessenger.getRestricted({
+        name: 'BridgeStatusController',
+        allowedActions: [
+          'AccountsController:getSelectedAccount',
+          'NetworkController:getNetworkClientById',
+          'NetworkController:findNetworkClientIdByChainId',
+          'NetworkController:getState',
+          'TransactionController:getState',
+        ],
+        allowedEvents: [],
+      }),
+      clientId: BridgeClientId.MOBILE,
+      fetchFn: fetch,
+    });
+
     this.context = {
       KeyringController: this.keyringController,
       AccountTrackerController: accountTrackerController,
@@ -1521,6 +1574,8 @@ export class Engine {
       MultichainAssetsRatesController: multichainAssetsRatesController,
       ///: END:ONLY_INCLUDE_IF
       MultichainNetworkController: multichainNetworkController,
+      BridgeController: bridgeController,
+      BridgeStatusController: bridgeStatusController,
     };
 
     const childControllers = Object.assign({}, this.context);
@@ -2146,6 +2201,8 @@ export default {
       MultichainAssetsController,
       ///: END:ONLY_INCLUDE_IF
       MultichainNetworkController,
+      BridgeController,
+      BridgeStatusController,
     } = instance.datamodel.state;
 
     return {
@@ -2189,6 +2246,8 @@ export default {
       MultichainAssetsController,
       ///: END:ONLY_INCLUDE_IF
       MultichainNetworkController,
+      BridgeController,
+      BridgeStatusController,
     };
   },
 
diff --git a/app/core/Engine/constants.ts b/app/core/Engine/constants.ts
index 4c83cfa2ea4..e20b722096f 100644
--- a/app/core/Engine/constants.ts
+++ b/app/core/Engine/constants.ts
@@ -60,4 +60,6 @@ export const BACKGROUND_STATE_CHANGE_EVENT_NAMES = [
   // TODO: Export this from the assets controller
   'MultichainAssetsController:stateChange',
   ///: END:ONLY_INCLUDE_IF
+  'BridgeController:stateChange',
+  'BridgeStatusController:stateChange',
 ] as const;
diff --git a/app/core/Engine/controllers/bridge-controller/constants.ts b/app/core/Engine/controllers/bridge-controller/constants.ts
new file mode 100644
index 00000000000..02e2bf08e54
--- /dev/null
+++ b/app/core/Engine/controllers/bridge-controller/constants.ts
@@ -0,0 +1,26 @@
+import type { BridgeControllerState } from '@metamask/bridge-controller';
+
+// Default BridgeControllerState
+export const defaultBridgeControllerState: BridgeControllerState = {
+  bridgeFeatureFlags: {
+    extensionConfig: {
+      refreshRate: 30000,
+      maxRefreshCount: 2,
+      support: true,
+      chains: {},
+    },
+    mobileConfig: {
+      refreshRate: 30000,
+      maxRefreshCount: 2,
+      support: true,
+      chains: {},
+    },
+  },
+  quoteRequest: {},
+  quotes: [],
+  quotesInitialLoadTime: null,
+  quotesLastFetched: null,
+  quotesLoadingStatus: null,
+  quoteFetchError: null,
+  quotesRefreshCount: 0,
+};
diff --git a/app/core/Engine/controllers/bridge-controller/index.test.ts b/app/core/Engine/controllers/bridge-controller/index.test.ts
new file mode 100644
index 00000000000..64bd3540120
--- /dev/null
+++ b/app/core/Engine/controllers/bridge-controller/index.test.ts
@@ -0,0 +1,147 @@
+import {
+  BridgeClientId,
+  BridgeController,
+  type BridgeControllerMessenger,
+} from '@metamask/bridge-controller';
+import type { ControllerInitRequest, Controllers } from '../../types';
+import { buildControllerInitRequestMock } from '../../utils/test-utils';
+import { bridgeControllerInit } from '.';
+import { ExtendedControllerMessenger } from '../../../ExtendedControllerMessenger';
+import { TransactionParams } from '@metamask/transaction-controller';
+import { ChainId } from '@metamask/controller-utils';
+import { defaultBridgeControllerState } from './constants';
+
+jest.mock('@metamask/bridge-controller');
+
+describe('bridge controller init', () => {
+  const bridgeControllerClassMock = jest.mocked(BridgeController);
+  let initRequestMock: jest.Mocked<
+    ControllerInitRequest<BridgeControllerMessenger>
+  >;
+
+  // Define mock dependencies
+  const mockFetch = jest.fn();
+  const mockGetLayer1GasFee = jest.fn();
+  // Create a minimal mock of the TransactionController
+  const mockTransactionController = {
+    getLayer1GasFee: mockGetLayer1GasFee,
+  } as unknown as Controllers['TransactionController'];
+
+  // Store the global fetch function
+  const originalFetch = global.fetch;
+
+  beforeEach(() => {
+    jest.resetAllMocks();
+
+    // Replace global fetch with mock
+    global.fetch = mockFetch;
+
+    const baseControllerMessenger = new ExtendedControllerMessenger();
+    // Create controller init request mock
+    initRequestMock = buildControllerInitRequestMock(baseControllerMessenger);
+
+    // Mock getController to return mockTransactionController
+    initRequestMock.getController.mockImplementation(
+      (controllerName: keyof Controllers) => {
+        if (controllerName === 'TransactionController') {
+          return mockTransactionController;
+        }
+        return undefined as unknown as Controllers[typeof controllerName];
+      },
+    );
+  });
+
+  afterEach(() => {
+    // Restore global fetch
+    global.fetch = originalFetch;
+  });
+
+  it('returns controller instance', () => {
+    expect(bridgeControllerInit(initRequestMock).controller).toBeInstanceOf(
+      BridgeController,
+    );
+  });
+
+  it('controller is initialized with the correct clientId', () => {
+    bridgeControllerInit(initRequestMock);
+    const bridgeControllerOptions = bridgeControllerClassMock.mock.calls[0][0];
+    expect(bridgeControllerOptions.clientId).toBe(BridgeClientId.MOBILE);
+  });
+
+  it('controller is initialized with the fetch function', () => {
+    bridgeControllerInit(initRequestMock);
+    const bridgeControllerOptions = bridgeControllerClassMock.mock.calls[0][0];
+    expect(bridgeControllerOptions.fetchFn).toBe(mockFetch);
+  });
+
+  it('controller state should be default state when no initial state is passed in', () => {
+    bridgeControllerInit(initRequestMock);
+    const bridgeControllerOptions = bridgeControllerClassMock.mock.calls[0][0];
+    expect(bridgeControllerOptions.state).toEqual(defaultBridgeControllerState);
+  });
+
+  it('controller state should be set to initial state when initial state is passed in', () => {
+    // Create a simple custom state that matches the structure
+    const customState = { ...defaultBridgeControllerState };
+
+    // Update mock with initial state
+    initRequestMock.persistedState = {
+      ...initRequestMock.persistedState,
+      BridgeController: customState,
+    };
+
+    bridgeControllerInit(initRequestMock);
+    const bridgeControllerOptions = bridgeControllerClassMock.mock.calls[0][0];
+    expect(bridgeControllerOptions.state).toEqual(customState);
+  });
+
+  it('controller is initialized with the provided messenger', () => {
+    bridgeControllerInit(initRequestMock);
+    const bridgeControllerOptions = bridgeControllerClassMock.mock.calls[0][0];
+    expect(bridgeControllerOptions.messenger).toBe(
+      initRequestMock.controllerMessenger,
+    );
+  });
+
+  it('gets TransactionController using getController', () => {
+    bridgeControllerInit(initRequestMock);
+    expect(initRequestMock.getController).toHaveBeenCalledWith(
+      'TransactionController',
+    );
+  });
+
+  it('sets up getLayer1GasFee correctly', async () => {
+    bridgeControllerInit(initRequestMock);
+
+    const bridgeControllerOptions = bridgeControllerClassMock.mock.calls[0][0];
+
+    // Extract the getLayer1GasFee function from the options
+    const getLayer1GasFeeFunc = bridgeControllerOptions.getLayer1GasFee;
+
+    // Set up mock return value
+    const mockReturnValue = { gasPrice: '0x1' };
+    mockGetLayer1GasFee.mockResolvedValueOnce(mockReturnValue);
+
+    // Test params with required 'from' property
+    const testParams: TransactionParams = {
+      to: '0x123',
+      from: '0x456',
+    };
+    const testChainId = '0x1' as ChainId;
+
+    // Call the function
+    const result = await getLayer1GasFeeFunc({
+      transactionParams: testParams,
+      chainId: testChainId,
+    });
+
+    // Verify mockGetLayer1GasFee was called with correct params
+    expect(mockGetLayer1GasFee).toHaveBeenCalledWith({
+      transactionParams: testParams,
+      chainId: testChainId,
+    });
+
+    // Verify the result is what mockGetLayer1GasFee returned
+    expect(result).toBe(mockReturnValue);
+  });
+});
diff --git a/app/core/Engine/controllers/bridge-controller/index.ts b/app/core/Engine/controllers/bridge-controller/index.ts
new file mode 100644
index 00000000000..e43d24420dd
--- /dev/null
+++ b/app/core/Engine/controllers/bridge-controller/index.ts
@@ -0,0 +1,50 @@
+import {
+  BridgeClientId,
+  BridgeController,
+  type BridgeControllerMessenger,
+  type BridgeControllerState,
+} from '@metamask/bridge-controller';
+import { TransactionParams } from '@metamask/transaction-controller';
+import { ChainId } from '@metamask/controller-utils';
+import { ControllerInitFunction } from '../../types';
+import { defaultBridgeControllerState } from './constants';
+
+/**
+ * Initialize the BridgeController.
+ *
+ * @param request - The request object.
+ * @returns The BridgeController.
+ */
+export const bridgeControllerInit: ControllerInitFunction<
+  BridgeController,
+  BridgeControllerMessenger
+> = (request) => {
+  const { controllerMessenger, getController, persistedState } = request;
+
+  const transactionController = getController('TransactionController');
+
+  const bridgeControllerState = (persistedState.BridgeController ??
+    defaultBridgeControllerState) as BridgeControllerState;
+
+  const controller = new BridgeController({
+    messenger: controllerMessenger,
+    clientId: BridgeClientId.MOBILE,
+    state: bridgeControllerState,
+    // TODO: change getLayer1GasFee type to match transactionController.getLayer1GasFee
+    getLayer1GasFee: async ({
+      transactionParams,
+      chainId,
+    }: {
+      transactionParams: TransactionParams;
+      chainId: ChainId;
+    }) =>
+      (await transactionController.getLayer1GasFee({
+        transactionParams,
+        chainId,
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      })) as any,
+    fetchFn: fetch,
+  });
+
+  return { controller };
+};
diff --git a/app/core/Engine/controllers/bridge-status-controller/constants.ts b/app/core/Engine/controllers/bridge-status-controller/constants.ts
new file mode 100644
index 00000000000..224c171dbdf
--- /dev/null
+++ b/app/core/Engine/controllers/bridge-status-controller/constants.ts
@@ -0,0 +1,6 @@
+import type { BridgeStatusControllerState } from '@metamask/bridge-status-controller';
+
+// Default BridgeStatusControllerState
+export const defaultBridgeStatusControllerState: BridgeStatusControllerState = {
+  txHistory: {},
+};
diff --git a/app/core/Engine/controllers/bridge-status-controller/index.test.ts b/app/core/Engine/controllers/bridge-status-controller/index.test.ts
new file mode 100644
index 00000000000..511d84c1946
--- /dev/null
+++ b/app/core/Engine/controllers/bridge-status-controller/index.test.ts
@@ -0,0 +1,95 @@
+import {
+  BridgeStatusController,
+  type BridgeStatusControllerMessenger,
+} from '@metamask/bridge-status-controller';
+import type { ControllerInitRequest } from '../../types';
+import { buildControllerInitRequestMock } from '../../utils/test-utils';
+import { bridgeStatusControllerInit } from '.';
+import { BridgeClientId } from '@metamask/bridge-controller';
+import { ExtendedControllerMessenger } from '../../../ExtendedControllerMessenger';
+import { defaultBridgeStatusControllerState } from './constants';
+
+jest.mock('@metamask/bridge-status-controller');
+
+describe('bridge status controller init', () => {
+  const bridgeStatusControllerClassMock = jest.mocked(BridgeStatusController);
+  let initRequestMock: jest.Mocked<
+    ControllerInitRequest<BridgeStatusControllerMessenger>
+  >;
+
+  // Define a mock fetch function
+  const mockFetch = jest.fn();
+
+  // Store the global fetch function
+  const originalFetch = global.fetch;
+
+  beforeEach(() => {
+    jest.resetAllMocks();
+
+    // Replace global fetch with mock
+    global.fetch = mockFetch;
+
+    const baseControllerMessenger = new ExtendedControllerMessenger();
+    // Create controller init request mock
+    initRequestMock = buildControllerInitRequestMock(baseControllerMessenger);
+  });
+
+  afterEach(() => {
+    // Restore global fetch
+    global.fetch = originalFetch;
+  });
+
+  it('returns controller instance', () => {
+    expect(
+      bridgeStatusControllerInit(initRequestMock).controller,
+    ).toBeInstanceOf(BridgeStatusController);
+  });
+
+  it('controller is initialized with the correct clientId', () => {
+    bridgeStatusControllerInit(initRequestMock);
+    const bridgeStatusControllerOptions =
+      bridgeStatusControllerClassMock.mock.calls[0][0];
+    expect(bridgeStatusControllerOptions.clientId).toBe(BridgeClientId.MOBILE);
+  });
+
+  it('controller is initialized with the fetch function', () => {
+    bridgeStatusControllerInit(initRequestMock);
+    const bridgeStatusControllerOptions =
+      bridgeStatusControllerClassMock.mock.calls[0][0];
+    expect(bridgeStatusControllerOptions.fetchFn).toBe(mockFetch);
+  });
+
+  it('controller state should be default state when no initial state is passed in', () => {
+    bridgeStatusControllerInit(initRequestMock);
+    const bridgeStatusControllerOptions =
+      bridgeStatusControllerClassMock.mock.calls[0][0];
+    expect(bridgeStatusControllerOptions.state).toEqual(
+      defaultBridgeStatusControllerState,
+    );
+  });
+
+  it('controller state should be set to initial state when initial state is passed in', () => {
+    // Create a simple custom state that matches the structure but with empty values
+    const customState = { ...defaultBridgeStatusControllerState };
+
+    // Update mock with initial state
+    initRequestMock.persistedState = {
+      ...initRequestMock.persistedState,
+      BridgeStatusController: customState,
+    };
+
+    bridgeStatusControllerInit(initRequestMock);
+    const bridgeStatusControllerOptions =
+      bridgeStatusControllerClassMock.mock.calls[0][0];
+    expect(bridgeStatusControllerOptions.state).toEqual(customState);
+  });
+
+  it('controller is initialized with the provided messenger', () => {
+    bridgeStatusControllerInit(initRequestMock);
+    const bridgeStatusControllerOptions =
+      bridgeStatusControllerClassMock.mock.calls[0][0];
+    expect(bridgeStatusControllerOptions.messenger).toBe(
+      initRequestMock.controllerMessenger,
+    );
+  });
+});
diff --git a/app/core/Engine/controllers/bridge-status-controller/index.ts b/app/core/Engine/controllers/bridge-status-controller/index.ts
new file mode 100644
index 00000000000..0742b708cdf
--- /dev/null
+++ b/app/core/Engine/controllers/bridge-status-controller/index.ts
@@ -0,0 +1,33 @@
+import {
+  BridgeStatusController,
+  type BridgeStatusControllerMessenger,
+  type BridgeStatusControllerState,
+} from '@metamask/bridge-status-controller';
+import { ControllerInitFunction } from '../../types';
+import { BridgeClientId } from '@metamask/bridge-controller';
+import { defaultBridgeStatusControllerState } from './constants';
+
+/**
+ * Initialize the BridgeStatusController.
+ *
+ * @param request - The request object.
+ * @returns The BridgeStatusController.
+ */
+export const bridgeStatusControllerInit: ControllerInitFunction<
+  BridgeStatusController,
+  BridgeStatusControllerMessenger
+> = (request) => {
+  const { controllerMessenger, persistedState } = request;
+
+  const bridgeStatusControllerState = (persistedState.BridgeStatusController ??
+    defaultBridgeStatusControllerState) as BridgeStatusControllerState;
+
+  const controller = new BridgeStatusController({
+    messenger: controllerMessenger,
+    state: bridgeStatusControllerState,
+    clientId: BridgeClientId.MOBILE,
+    fetchFn: fetch,
+  });
+
+  return { controller };
+};
diff --git a/app/core/Engine/messengers/bridge-controller-messenger/index.ts b/app/core/Engine/messengers/bridge-controller-messenger/index.ts
new file mode 100644
index 00000000000..844c597975c
--- /dev/null
+++ b/app/core/Engine/messengers/bridge-controller-messenger/index.ts
@@ -0,0 +1,23 @@
+import { BridgeControllerMessenger } from '@metamask/bridge-controller';
+import { BaseControllerMessenger } from '../../types';
+
+/**
+ * Get the BridgeControllerMessenger for the BridgeController.
+ *
+ * @param baseControllerMessenger - The base controller messenger.
+ * @returns The BridgeControllerMessenger.
+ */
+export function getBridgeControllerMessenger(
+  baseControllerMessenger: BaseControllerMessenger,
+): BridgeControllerMessenger {
+  return baseControllerMessenger.getRestricted({
+    name: 'BridgeController',
+    allowedActions: [
+      'AccountsController:getSelectedAccount',
+      'NetworkController:findNetworkClientIdByChainId',
+      'NetworkController:getState',
+      'NetworkController:getNetworkClientById',
+    ],
+    allowedEvents: [],
+  });
+}
diff --git a/app/core/Engine/messengers/bridge-status-controller-messenger/index.ts b/app/core/Engine/messengers/bridge-status-controller-messenger/index.ts
new file mode 100644
index 00000000000..b0f34ea2bf1
--- /dev/null
+++ b/app/core/Engine/messengers/bridge-status-controller-messenger/index.ts
@@ -0,0 +1,24 @@
+import { BridgeStatusControllerMessenger } from '@metamask/bridge-status-controller';
+import { BaseControllerMessenger } from '../../types';
+
+/**
+ * Get the BridgeControllerMessenger for the BridgeController.
+ *
+ * @param baseControllerMessenger - The base controller messenger.
+ * @returns The BridgeControllerMessenger.
+ */
+export function getBridgeStatusControllerMessenger(
+  baseControllerMessenger: BaseControllerMessenger,
+): BridgeStatusControllerMessenger {
+  return baseControllerMessenger.getRestricted({
+    name: 'BridgeStatusController',
+    allowedActions: [
+      'AccountsController:getSelectedAccount',
+      'NetworkController:getNetworkClientById',
+      'NetworkController:findNetworkClientIdByChainId',
+      'NetworkController:getState',
+      'TransactionController:getState',
+    ],
+    allowedEvents: [],
+  });
+}
diff --git a/app/core/Engine/types.ts b/app/core/Engine/types.ts
index 839f60af4be..825947cd40a 100644
--- a/app/core/Engine/types.ts
+++ b/app/core/Engine/types.ts
@@ -225,6 +225,18 @@ import {
   MultichainNetworkControllerState,
   MultichainNetworkControllerEvents,
 } from '@metamask/multichain-network-controller';
+import {
+  BridgeController,
+  BridgeControllerActions,
+  type BridgeControllerEvents,
+  type BridgeControllerState,
+} from '@metamask/bridge-controller';
+import type {
+  BridgeStatusController,
+  BridgeStatusControllerActions,
+  BridgeStatusControllerEvents,
+  BridgeStatusControllerState,
+} from '@metamask/bridge-status-controller';
 
 /**
  * Controllers that area always instantiated
@@ -304,7 +316,9 @@ type GlobalActions =
   | AssetsContractControllerActions
   | RemoteFeatureFlagControllerActions
   | TokenSearchDiscoveryControllerActions
-  | MultichainNetworkControllerActions;
+  | MultichainNetworkControllerActions
+  | BridgeControllerActions
+  | BridgeStatusControllerActions;
 
 type GlobalEvents =
   | ComposableControllerEvents<EngineState>
@@ -349,7 +363,9 @@ type GlobalEvents =
   | RemoteFeatureFlagControllerEvents
   | TokenSearchDiscoveryControllerEvents
   | SnapKeyringEvents
-  | MultichainNetworkControllerEvents;
+  | MultichainNetworkControllerEvents
+  | BridgeControllerEvents
+  | BridgeStatusControllerEvents;
 
 // TODO: Abstract this into controller utils for TransactionController
 export interface TransactionEventPayload {
@@ -423,6 +439,8 @@ export type Controllers = {
   MultichainAssetsController: MultichainAssetsController;
   ///: END:ONLY_INCLUDE_IF
   MultichainNetworkController: MultichainNetworkController;
+  BridgeController: BridgeController;
+  BridgeStatusController: BridgeStatusController;
 };
 
 /**
@@ -480,6 +498,8 @@ export type EngineState = {
   MultichainAssetsRatesController: MultichainAssetsRatesControllerState;
   ///: END:ONLY_INCLUDE_IF
   MultichainNetworkController: MultichainNetworkControllerState;
+  BridgeController: BridgeControllerState;
+  BridgeStatusController: BridgeStatusControllerState;
 };
 
 /** Controller names */
diff --git a/app/util/logs/__snapshots__/index.test.ts.snap b/app/util/logs/__snapshots__/index.test.ts.snap
index 341edf23b3a..ad3aab70b1c 100644
--- a/app/util/logs/__snapshots__/index.test.ts.snap
+++ b/app/util/logs/__snapshots__/index.test.ts.snap
@@ -29,6 +29,35 @@ exports[`logs :: generateStateLogs generates a valid json export 1`] = `
       "AuthenticationController": {
         "isSignedIn": false,
       },
+      "BridgeController": {
+        "bridgeFeatureFlags": {
+          "extensionConfig": {
+            "chains": {},
+            "maxRefreshCount": 5,
+            "refreshRate": 30000,
+            "support": false,
+          },
+          "mobileConfig": {
+            "chains": {},
+            "maxRefreshCount": 5,
+            "refreshRate": 30000,
+            "support": false,
+          },
+        },
+        "quoteFetchError": null,
+        "quoteRequest": {
+          "slippage": 0.5,
+          "srcTokenAddress": "0x0000000000000000000000000000000000000000",
+        },
+        "quotes": [],
+        "quotesInitialLoadTime": null,
+        "quotesLastFetched": null,
+        "quotesLoadingStatus": null,
+        "quotesRefreshCount": 0,
+      },
+      "BridgeStatusController": {
+        "txHistory": {},
+      },
       "CronjobController": {
         "events": {},
         "jobs": {},
diff --git a/app/util/test/initial-background-state.json b/app/util/test/initial-background-state.json
index 14d049f082e..d99a9a7571d 100644
--- a/app/util/test/initial-background-state.json
+++ b/app/util/test/initial-background-state.json
@@ -377,6 +377,35 @@
     "accountsAssets": {},
     "assetsMetadata": {}
   },
+  "BridgeController": {
+    "bridgeFeatureFlags": {
+      "extensionConfig": {
+        "chains": {},
+        "maxRefreshCount": 5,
+        "refreshRate": 30000,
+        "support": false
+      },
+      "mobileConfig": {
+        "chains": {},
+        "maxRefreshCount": 5,
+        "refreshRate": 30000,
+        "support": false
+      }
+    },
+    "quoteFetchError": null,
+    "quoteRequest": {
+      "slippage": 0.5,
+      "srcTokenAddress": "0x0000000000000000000000000000000000000000"
+    },
+    "quotes": [],
+    "quotesInitialLoadTime": null,
+    "quotesLastFetched": null,
+    "quotesLoadingStatus": null,
+    "quotesRefreshCount": 0
+  },
+  "BridgeStatusController": {
+    "txHistory": {}
+  },
   "MultichainAssetsRatesController": {
     "conversionRates": {}
   },
diff --git a/babel.config.js b/babel.config.js
index 349c0cecdef..5ae99b34e7a 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -19,6 +19,10 @@ module.exports = {
       test: './node_modules/@metamask/notification-services-controller',
       plugins: [['@babel/plugin-transform-private-methods', { loose: true }]],
     },
+    {
+      test: './node_modules/@metamask/bridge-controller',
+      plugins: [['@babel/plugin-transform-private-methods', { loose: true }]],
+    },
     {
       test: [
         './node_modules/**/@metamask/rpc-errors/**',
diff --git a/package.json b/package.json
index 7100a6fd871..b9cdad33793 100644
--- a/package.json
+++ b/package.json
@@ -157,6 +157,8 @@
     "@metamask/assets-controllers": "^51.0.2",
     "@metamask/base-controller": "^8.0.0",
     "@metamask/bitcoin-wallet-snap": "^0.9.0",
+    "@metamask/bridge-controller": "^3.0.0",
+    "@metamask/bridge-status-controller": "^3.0.0",
     "@metamask/composable-controller": "^11.0.0",
     "@metamask/controller-utils": "^11.3.0",
     "@metamask/design-tokens": "^5.0.0",
diff --git a/yarn.lock b/yarn.lock
index e4cec4e962a..3deb2b28977 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4608,6 +4608,34 @@
   resolved "https://registry.yarnpkg.com/@metamask/bitcoin-wallet-snap/-/bitcoin-wallet-snap-0.9.0.tgz#47014c75c1e1ed84fef57177901f458fd2b6510a"
   integrity sha512-983twhfOfSCQljT+eV8JLcV0bqGVMObXnEy8TOFAkwwRW2I1ZsVRAfylbGiSwxlhxLntcpLqVu0Sj29xNyA06Q==
 
+"@metamask/bridge-controller@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@metamask/bridge-controller/-/bridge-controller-3.0.0.tgz#7dc45f8ba3a451d1e1c505dfb26f45aa4143cd09"
+  integrity sha512-7k2s3FxQorz/vi7XWmmXIjI7QNcmtoUBNZtXzAwu4W44YAZdY3ojVdA20qCnHQW1NkSHn44eaacfZynqiZmQ1Q==
+  dependencies:
+    "@ethersproject/address" "^5.7.0"
+    "@ethersproject/bignumber" "^5.7.0"
+    "@ethersproject/constants" "^5.7.0"
+    "@ethersproject/contracts" "^5.7.0"
+    "@ethersproject/providers" "^5.7.0"
+    "@metamask/base-controller" "^8.0.0"
+    "@metamask/controller-utils" "^11.5.0"
+    "@metamask/metamask-eth-abis" "^3.1.1"
+    "@metamask/polling-controller" "^12.0.3"
+    "@metamask/utils" "^11.2.0"
+
+"@metamask/bridge-status-controller@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@metamask/bridge-status-controller/-/bridge-status-controller-3.0.0.tgz#472416e8f8e2c3ea159560f95c3078a351f87c73"
+  integrity sha512-SRXiIk7TNs2vUmckILHkaev3IGtpwUwoX8b+Hcg0VQ3aEH/8+cQFSLh3JHRfFHTkj9vTrsYRUj4qobMGIjElUQ==
+  dependencies:
+    "@metamask/base-controller" "^8.0.0"
+    "@metamask/bridge-controller" "^3.0.0"
+    "@metamask/controller-utils" "^11.5.0"
+    "@metamask/polling-controller" "^12.0.3"
+    "@metamask/superstruct" "^3.1.0"
+    "@metamask/utils" "^11.2.0"
+
 "@metamask/browser-passworder@^4.3.0":
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/@metamask/browser-passworder/-/browser-passworder-4.3.0.tgz#62c200750efcea864bd31d685120331859e1ab1e"
@@ -10099,12 +10127,12 @@
     "@types/node" "*"
     form-data "^4.0.0"
 
-"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^20.12.8":
-  version "20.14.11"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.11.tgz#09b300423343460455043ddd4d0ded6ac579b74b"
-  integrity sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==
+"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0":
+  version "22.7.5"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b"
+  integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==
   dependencies:
-    undici-types "~5.26.4"
+    undici-types "~6.19.2"
 
 "@types/node@^17.0.31", "@types/node@^17.0.4":
   version "17.0.45"
@@ -10116,6 +10144,13 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.1.tgz#80b22f3df719f15c9736207980e95f35d01ec1aa"
   integrity sha512-3G42sxmm0fF2+Vtb9TJQpnjmP+uKlWvFa8KoEGquh4gqRmoUG/N0ufuhikw6HEsdG2G2oIKhog1GCTfz9v5NdQ==
 
+"@types/node@^20.12.8":
+  version "20.14.11"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.11.tgz#09b300423343460455043ddd4d0ded6ac579b74b"
+  integrity sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==
+  dependencies:
+    undici-types "~5.26.4"
+
 "@types/normalize-package-data@^2.4.0":
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
@@ -28485,6 +28520,11 @@ undici-types@~5.26.4:
   resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
   integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
 
+undici-types@~6.19.2:
+  version "6.19.8"
+  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
+  integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
+
 undici@5.28.4, undici@5.28.5:
   version "5.28.5"
   resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.5.tgz#b2b94b6bf8f1d919bc5a6f31f2c01deb02e54d4b"


Reviews:
user: login: bfullam
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T22:03:43Z
user: login: bfullam
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T22:38:26Z
user: login: tommasini
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T15:14:07Z
user: login: tommasini
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T15:14:22Z
user: login: amitabh94
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T15:17:04Z
user: login: amitabh94
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T15:17:13Z
user: login: tommasini
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T15:20:49Z
user: login: tommasini
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T15:21:15Z
user: login: bfullam
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T13:14:43Z
user: login: tommasini
body: Let's re run the e2e?
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T14:52:52Z

Review comments:
diff_hunk: @@ -1339,6 +1346,52 @@ export class Engine {
       getMetaMetricsProps: () => Promise.resolve({}), // Return MetaMetrics props once we enable HW wallets for smart transactions.
     });
 
+    /* bridge controller Initialization */
+
+    const bridgeController = new BridgeController({
path: app/core/Engine/Engine.ts
user: login: tommasini
body: We should use the new pattern to add new controllers!

The PR that added the new pattern is this one: https://github.com/MetaMask/metamask-mobile/pull/13065
That will allow to keep a better code ownership per team 
created_at: 2025-03-04T15:14:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1339,6 +1346,52 @@ export class Engine {
       getMetaMetricsProps: () => Promise.resolve({}), // Return MetaMetrics props once we enable HW wallets for smart transactions.
     });
 
+    /* bridge controller Initialization */
+
+    const bridgeController = new BridgeController({
+      messenger: this.controllerMessenger.getRestricted({
+        name: 'BridgeController',
+        allowedActions: [
+          'AccountsController:getSelectedAccount',
+          'NetworkController:findNetworkClientIdByChainId',
+          'NetworkController:getState',
+          'NetworkController:getNetworkClientById',
+        ],
+        allowedEvents: [],
+      }),
+      clientId: BridgeClientId.MOBILE,
+      // TODO: change getLayer1GasFee type to match transactionController.getLayer1GasFee
+      getLayer1GasFee: async ({
+        transactionParams,
+        chainId,
+      }: {
+        transactionParams: TransactionParams;
+        chainId: ChainId;
+      }) =>
+        this.transactionController.getLayer1GasFee({
+          transactionParams,
+          chainId,
+          // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        }) as any,
+      fetchFn: fetch,
+    });
+
+    const bridgeStatusController = new BridgeStatusController({
path: app/core/Engine/Engine.ts
user: login: tommasini
body: Same here: https://github.com/MetaMask/metamask-mobile/pull/13750/files#r1979662697
created_at: 2025-03-04T15:14:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1339,6 +1346,52 @@ export class Engine {
       getMetaMetricsProps: () => Promise.resolve({}), // Return MetaMetrics props once we enable HW wallets for smart transactions.
     });
 
+    /* bridge controller Initialization */
+
+    const bridgeController = new BridgeController({
path: app/core/Engine/Engine.ts
user: login: amitabh94
body: I think some more work needs to be done before new controllers can be added using this pattern. 
https://consensys.slack.com/archives/C02U025CVU4/p1740668625282689?thread_ts=1740168363.987169&cid=C02U025CVU4
created_at: 2025-03-04T15:17:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1339,6 +1346,52 @@ export class Engine {
       getMetaMetricsProps: () => Promise.resolve({}), // Return MetaMetrics props once we enable HW wallets for smart transactions.
     });
 
+    /* bridge controller Initialization */
+
+    const bridgeController = new BridgeController({
+      messenger: this.controllerMessenger.getRestricted({
+        name: 'BridgeController',
+        allowedActions: [
+          'AccountsController:getSelectedAccount',
+          'NetworkController:findNetworkClientIdByChainId',
+          'NetworkController:getState',
+          'NetworkController:getNetworkClientById',
+        ],
+        allowedEvents: [],
+      }),
+      clientId: BridgeClientId.MOBILE,
+      // TODO: change getLayer1GasFee type to match transactionController.getLayer1GasFee
+      getLayer1GasFee: async ({
+        transactionParams,
+        chainId,
+      }: {
+        transactionParams: TransactionParams;
+        chainId: ChainId;
+      }) =>
+        this.transactionController.getLayer1GasFee({
+          transactionParams,
+          chainId,
+          // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        }) as any,
+      fetchFn: fetch,
+    });
+
+    const bridgeStatusController = new BridgeStatusController({
path: app/core/Engine/Engine.ts
user: login: amitabh94
body: Same comment as above.
created_at: 2025-03-04T15:17:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1339,6 +1346,52 @@ export class Engine {
       getMetaMetricsProps: () => Promise.resolve({}), // Return MetaMetrics props once we enable HW wallets for smart transactions.
     });
 
+    /* bridge controller Initialization */
+
+    const bridgeController = new BridgeController({
path: app/core/Engine/Engine.ts
user: login: tommasini
body: Ah cool, thank you for pointing to the right direciton! Since this topic was already being talked, I will approve the PR!
created_at: 2025-03-04T15:20:49Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-02-27T17:03:55Z
body: **CLA Signature Action:** All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.
author_association: CONTRIBUTOR
user: login: socket-security[bot]
created_at: 2025-02-27T17:05:17Z
body: <!-- overview-comment -->
**New, updated, and removed dependencies detected.** Learn more about [Socket for GitHub ↗︎](https://socket.dev?utm_medium=gh)


| Package | New capabilities | Transitives | Size | Publisher |
|:--- |:--- |:--- |:--- |:--- |
| [npm/@metamask/bridge-controller@3.0.0](https://socket.dev/npm/package/@metamask/bridge-controller/overview/3.0.0) | <a href="https://socket.dev/npm/package/@metamask/bridge-controller/alerts/3.0.0?alert_name=envVars">environment</a> | <a href="https://socket.dev/npm/package/@metamask/bridge-controller/dependencies/3.0.0">`0`</a> | 353 kB | <a href="https://socket.dev/npm/user/danfinlay">danfinlay</a>, <a href="https://socket.dev/npm/user/gudahtt">gudahtt</a>, <a href="https://socket.dev/npm/user/kumavis">kumavis</a>, ...6 more |
| [npm/@metamask/bridge-status-controller@3.0.0](https://socket.dev/npm/package/@metamask/bridge-status-controller/overview/3.0.0) | None | <a href="https://socket.dev/npm/package/@metamask/bridge-status-controller/dependencies/3.0.0">`0`</a> | 174 kB | <a href="https://socket.dev/npm/user/metamaskbot">metamaskbot</a> |
| [npm/@types/node@20.14.11](https://socket.dev/npm/package/@types/node/overview/20.14.11) ➜ [22.7.5](https://socket.dev/npm/package/@types/node/overview/22.7.5) | None | <a href="https://socket.dev/npm/package/@types/node/dependencies/22.7.5">`0`</a> | 2.25 MB | <a href="https://socket.dev/npm/user/types">types</a> |


**🚮 Removed packages:** [npm/undici-types@5.26.5](https://socket.dev/npm/package/undici-types/overview/5.26.5)



[**View full report↗︎**](https://socket.dev/dashboard/org/MetaMask/diff/2d05d890-cbbb-4fb1-ba70-0f3748f73e16/a0fac1b2-1beb-4ade-96a8-c1374869bf65)

###### Have feedback? Participate in our [User Experience Survey](https://app.formbricks.com/s/cm71tukp20001jr038b70ccid) 📊
author_association: NONE
user: login: socket-security[bot]
created_at: 2025-02-27T17:05:18Z
body: **👍 Dependency issues cleared.** Learn more about [Socket for GitHub ↗︎](https://socket.dev?utm_medium=gh)

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.





[**View full report↗︎**](https://socket.dev/dashboard/org/MetaMask/diff/2d05d890-cbbb-4fb1-ba70-0f3748f73e16/a0fac1b2-1beb-4ade-96a8-c1374869bf65)
author_association: NONE
user: login: amitabh94
created_at: 2025-02-27T22:36:54Z
body: @SocketSecurity ignore npm/ethers@6.13.5
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-02-28T21:48:37Z
body: ## [<img alt="https://bitrise.io/" src="https://assets-global.website-files.com/5db35de024bb983af1b4e151/5e6f9ccc3e129dfd8a205e4e_Bitrise%20Logo%20-%20Eggplant%20Bg.png" height="20">](https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/721ab6f1-62d1-4e9c-8393-7c7075c774de) **Bitrise**

❌❌❌ `pr_smoke_e2e_pipeline` failed on Bitrise! ❌❌❌

Commit hash: 4e7319ccb10c95836e061837dff6e8e941cd2314
Build link: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/721ab6f1-62d1-4e9c-8393-7c7075c774de

>[!NOTE]
>- You can kick off another `pr_smoke_e2e_pipeline` on Bitrise by removing and re-applying the `Run Smoke E2E` label on the pull request

> [!TIP]
>- Check the [documentation](https://www.notion.so/metamask-consensys/Bitrise-Pipeline-Overview-43159500c43748a389556f0593e8834b#26052f2ea6e24f8c9cfdb57a7522dc1f) if you have any doubts on how to understand the failure on bitrise

<!-- BITRISE_TAG -->
<!-- BITRISE_FAIL_TAG -->

<!-- 4e7319ccb10c95836e061837dff6e8e941cd2314 -->
author_association: CONTRIBUTOR
user: login: codecov-commenter
created_at: 2025-02-28T22:27:53Z
body: ## [Codecov](https://app.codecov.io/gh/MetaMask/metamask-mobile/pull/13750?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask) Report
Attention: Patch coverage is `84.21053%` with `3 lines` in your changes missing coverage. Please review.
> Project coverage is 63.55%. Comparing base [(`d1d22d7`)](https://app.codecov.io/gh/MetaMask/metamask-mobile/commit/d1d22d72d3871336b1b25eb2de191dea407aae77?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask) to head [(`4a162cb`)](https://app.codecov.io/gh/MetaMask/metamask-mobile/commit/4a162cba1345611a6aa81f848ea3d2af9066c333?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask).
> Report is 7 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/MetaMask/metamask-mobile/pull/13750?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask) | Patch % | Lines |
|---|---|---|
| [app/core/Engine/Engine.ts](https://app.codecov.io/gh/MetaMask/metamask-mobile/pull/13750?src=pr&el=tree&filepath=app%2Fcore%2FEngine%2FEngine.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask#diff-YXBwL2NvcmUvRW5naW5lL0VuZ2luZS50cw==) | 66.66% | [1 Missing :warning: ](https://app.codecov.io/gh/MetaMask/metamask-mobile/pull/13750?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask) |
| [...ne/messengers/bridge-controller-messenger/index.ts](https://app.codecov.io/gh/MetaMask/metamask-mobile/pull/13750?src=pr&el=tree&filepath=app%2Fcore%2FEngine%2Fmessengers%2Fbridge-controller-messenger%2Findex.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask#diff-YXBwL2NvcmUvRW5naW5lL21lc3NlbmdlcnMvYnJpZGdlLWNvbnRyb2xsZXItbWVzc2VuZ2VyL2luZGV4LnRz) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/MetaMask/metamask-mobile/pull/13750?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask) |
| [...engers/bridge-status-controller-messenger/index.ts](https://app.codecov.io/gh/MetaMask/metamask-mobile/pull/13750?src=pr&el=tree&filepath=app%2Fcore%2FEngine%2Fmessengers%2Fbridge-status-controller-messenger%2Findex.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask#diff-YXBwL2NvcmUvRW5naW5lL21lc3NlbmdlcnMvYnJpZGdlLXN0YXR1cy1jb250cm9sbGVyLW1lc3Nlbmdlci9pbmRleC50cw==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/MetaMask/metamask-mobile/pull/13750?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main   #13750      +/-   ##
==========================================
+ Coverage   63.42%   63.55%   +0.12%     
==========================================
  Files        2093     2101       +8     
  Lines       45277    45329      +52     
  Branches     6234     6244      +10     
==========================================
+ Hits        28717    28807      +90     
+ Misses      14676    14640      -36     
+ Partials     1884     1882       -2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/MetaMask/metamask-mobile/pull/13750?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask).

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-02-28T22:29:13Z
body: ## [<img alt="https://bitrise.io/" src="https://assets-global.website-files.com/5db35de024bb983af1b4e151/5e6f9ccc3e129dfd8a205e4e_Bitrise%20Logo%20-%20Eggplant%20Bg.png" height="20">](https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/87116063-ccca-4ed3-8aea-074a3ffcc97a) **Bitrise**

❌❌❌ `pr_smoke_e2e_pipeline` failed on Bitrise! ❌❌❌

Commit hash: 4a162cba1345611a6aa81f848ea3d2af9066c333
Build link: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/87116063-ccca-4ed3-8aea-074a3ffcc97a

>[!NOTE]
>- You can kick off another `pr_smoke_e2e_pipeline` on Bitrise by removing and re-applying the `Run Smoke E2E` label on the pull request

> [!TIP]
>- Check the [documentation](https://www.notion.so/metamask-consensys/Bitrise-Pipeline-Overview-43159500c43748a389556f0593e8834b#26052f2ea6e24f8c9cfdb57a7522dc1f) if you have any doubts on how to understand the failure on bitrise

<!-- BITRISE_TAG -->
<!-- BITRISE_FAIL_TAG -->

<!-- 4a162cba1345611a6aa81f848ea3d2af9066c333 -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-03T23:24:36Z
body: ## [<img alt="https://bitrise.io/" src="https://assets-global.website-files.com/5db35de024bb983af1b4e151/5e6f9ccc3e129dfd8a205e4e_Bitrise%20Logo%20-%20Eggplant%20Bg.png" height="20">](https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/6cf9c69b-199b-4a49-a29e-8ea07a5d5557) **Bitrise**

❌❌❌ `pr_smoke_e2e_pipeline` failed on Bitrise! ❌❌❌

Commit hash: cce25e165b92a0eba2d090f4b866fe50c42e41ee
Build link: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/6cf9c69b-199b-4a49-a29e-8ea07a5d5557

>[!NOTE]
>- You can kick off another `pr_smoke_e2e_pipeline` on Bitrise by removing and re-applying the `Run Smoke E2E` label on the pull request

> [!TIP]
>- Check the [documentation](https://www.notion.so/metamask-consensys/Bitrise-Pipeline-Overview-43159500c43748a389556f0593e8834b#26052f2ea6e24f8c9cfdb57a7522dc1f) if you have any doubts on how to understand the failure on bitrise

<!-- BITRISE_TAG -->
<!-- BITRISE_FAIL_TAG -->

<!-- cce25e165b92a0eba2d090f4b866fe50c42e41ee -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-03T23:59:49Z
body: ## [<img alt="https://bitrise.io/" src="https://assets-global.website-files.com/5db35de024bb983af1b4e151/5e6f9ccc3e129dfd8a205e4e_Bitrise%20Logo%20-%20Eggplant%20Bg.png" height="20">](https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/17ec23d7-747a-403f-8dfc-31ff1359f397) **Bitrise**

✅✅✅ `pr_smoke_e2e_pipeline` passed on Bitrise! ✅✅✅

Commit hash: 487519ee0e5da6665bbd43cf8e769b6f267ec787
Build link: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/17ec23d7-747a-403f-8dfc-31ff1359f397

>[!NOTE]
>- You can kick off another `pr_smoke_e2e_pipeline` on Bitrise by removing and re-applying the `Run Smoke E2E` label on the pull request

<!-- BITRISE_TAG -->
<!-- BITRISE_SUCCESS_TAG -->

<!-- 487519ee0e5da6665bbd43cf8e769b6f267ec787 -->
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-05T01:11:01Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=metamask-mobile&pullRequest=13750) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=metamask-mobile&pullRequest=13750&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=metamask-mobile&pullRequest=13750&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=metamask-mobile&pullRequest=13750&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [87.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=metamask-mobile&pullRequest=13750&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=metamask-mobile&pullRequest=13750&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=metamask-mobile&pullRequest=13750)


author_association: NONE
user: login: Rorrote
created_at: 2025-03-05T01:44:25Z
body: > <!--
> Please submit this PR as a draft initially.
> Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once.
> -->
> 
> ## **Description**
> 
> This PR adds BridgeController and BridgeStatus Controllers to the Engine file and initializes them for use in the client codebase.
> This also initializes the Bridge state which would be used by Bridge UI components.
> 
> This PR currently places the initialization of controllers directly int he Engine file rather than using the new modular structure as few things need to be ironed out with the modular approach regarding ordering of controllers and their dependencies.
> 
> This PR however creates new directories for the modular approach as well and will use them when the issue with modularization of controllers is fixed.
> 
> ## **Related issues**
> 
> Fixes: [SWAPS-1981](https://consensyssoftware.atlassian.net/browse/MMS-1981)
> 
> ## **Manual testing steps**
> 
> 1. Launch the MetaMask Mobile app
> 2. Verify the app initializes without errors (check console for any Bridge Controller related errors)
> 3. Confirm the app state includes the new Bridge Controller entries:
>    - BridgeController
>    - BridgeStatusController
> 
> ## **Screenshots/Recordings**
> 
> <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. -->
> 
> ### **Before**
> 
> <!-- [screenshots/recordings] -->
> 
> ### **After**
> 
> <!-- [screenshots/recordings] -->
> 
> ## **Pre-merge author checklist**
> 
> - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
> - [x] I've completed the PR template to the best of my ability
> - [x] I’ve included tests if applicable
> - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable
> - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors.
> 
> ## **Pre-merge reviewer checklist**
> 
> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
> - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
> 


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-05T14:54:33Z
body: ## [<img alt="https://bitrise.io/" src="https://assets-global.website-files.com/5db35de024bb983af1b4e151/5e6f9ccc3e129dfd8a205e4e_Bitrise%20Logo%20-%20Eggplant%20Bg.png" height="20">](https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/eac3f889-58e1-43a0-bebc-e14aaadb4064) **Bitrise**

❌❌❌ `pr_smoke_e2e_pipeline` failed on Bitrise! ❌❌❌

Commit hash: 41a0196b1e1c816702906591d15173fb52ab0e70
Build link: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/eac3f889-58e1-43a0-bebc-e14aaadb4064

>[!NOTE]
>- You can kick off another `pr_smoke_e2e_pipeline` on Bitrise by removing and re-applying the `Run Smoke E2E` label on the pull request

> [!TIP]
>- Check the [documentation](https://www.notion.so/metamask-consensys/Bitrise-Pipeline-Overview-43159500c43748a389556f0593e8834b#26052f2ea6e24f8c9cfdb57a7522dc1f) if you have any doubts on how to understand the failure on bitrise

<!-- BITRISE_TAG -->
<!-- BITRISE_FAIL_TAG -->

<!-- 41a0196b1e1c816702906591d15173fb52ab0e70 -->
author_association: CONTRIBUTOR