From 8343c852325682a8dc01bbd635f630ae90c182d0 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:48:22 -0500 Subject: [PATCH 01/49] chore: update yarn lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 86a3b4cfc3..500874473b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13954,4 +13954,4 @@ __metadata: "@types/yoga-layout": "npm:1.9.2" checksum: 10/fe36fadae9b30710083f76c73e87479c2eb291ff7c560c35a9e2b8eb78f43882ace63cc80cdaecae98ee2e4168e1bf84dc65b2f5ae1bfa31df37603c46683bd6 languageName: node - linkType: hard + linkType: hard \ No newline at end of file From 624bc83c0e6fedd1c9d19a325e3aa743c23a746b Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:08:18 -0500 Subject: [PATCH 02/49] chore: setup skeleton for bridge status controller --- packages/bridge-status-controller/LICENSE | 20 +++++ packages/bridge-status-controller/README.md | 15 ++++ .../bridge-status-controller/jest.config.js | 26 ++++++ .../bridge-status-controller/package.json | 87 +++++++++++++++++++ .../tsconfig.build.json | 19 ++++ .../bridge-status-controller/tsconfig.json | 16 ++++ .../bridge-status-controller/typedoc.json | 7 ++ 7 files changed, 190 insertions(+) create mode 100644 packages/bridge-status-controller/LICENSE create mode 100644 packages/bridge-status-controller/README.md create mode 100644 packages/bridge-status-controller/jest.config.js create mode 100644 packages/bridge-status-controller/package.json create mode 100644 packages/bridge-status-controller/tsconfig.build.json create mode 100644 packages/bridge-status-controller/tsconfig.json create mode 100644 packages/bridge-status-controller/typedoc.json diff --git a/packages/bridge-status-controller/LICENSE b/packages/bridge-status-controller/LICENSE new file mode 100644 index 0000000000..7d002dced3 --- /dev/null +++ b/packages/bridge-status-controller/LICENSE @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) 2025 MetaMask + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE diff --git a/packages/bridge-status-controller/README.md b/packages/bridge-status-controller/README.md new file mode 100644 index 0000000000..adb050aede --- /dev/null +++ b/packages/bridge-status-controller/README.md @@ -0,0 +1,15 @@ +# `@metamask/bridge-controller` + +Manages bridge-related quote fetching functionality for MetaMask. + +## Installation + +`yarn add @metamask/bridge-controller` + +or + +`npm install @metamask/bridge-controller` + +## Contributing + +This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme). diff --git a/packages/bridge-status-controller/jest.config.js b/packages/bridge-status-controller/jest.config.js new file mode 100644 index 0000000000..c8fc07a065 --- /dev/null +++ b/packages/bridge-status-controller/jest.config.js @@ -0,0 +1,26 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +const merge = require('deepmerge'); +const path = require('path'); + +const baseConfig = require('../../jest.config.packages'); + +const displayName = path.basename(__dirname); + +module.exports = merge(baseConfig, { + // The display name when running multiple projects + displayName, + + // An object that configures minimum threshold enforcement for coverage results + coverageThreshold: { + global: { + branches: 89, + functions: 98, + lines: 98, + statements: 98, + }, + }, +}); diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json new file mode 100644 index 0000000000..2b643ed206 --- /dev/null +++ b/packages/bridge-status-controller/package.json @@ -0,0 +1,87 @@ +{ + "name": "@metamask/bridge-status-controller", + "version": "1.0.0", + "description": "Manages bridge-related status fetching functionality for MetaMask", + "keywords": [ + "MetaMask", + "Ethereum" + ], + "homepage": "https://github.com/MetaMask/core/tree/main/packages/bridge-status-controller#readme", + "bugs": { + "url": "https://github.com/MetaMask/core/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/core.git" + }, + "license": "MIT", + "sideEffects": false, + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.cts", + "files": [ + "dist/" + ], + "scripts": { + "build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references", + "build:docs": "typedoc", + "changelog:update": "../../scripts/update-changelog.sh @metamask/bridge-status-controller", + "changelog:validate": "../../scripts/validate-changelog.sh @metamask/bridge-status-controller", + "publish:preview": "yarn npm publish --tag preview", + "since-latest-release": "../../scripts/since-latest-release.sh", + "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter", + "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache", + "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose", + "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch" + }, + "dependencies": { + "@metamask/accounts-controller": "^23.0.0", + "@metamask/base-controller": "^7.1.1", + "@metamask/controller-utils": "^11.5.0", + "@metamask/metamask-eth-abis": "^3.1.1", + "@metamask/network-controller": "^22.2.0", + "@metamask/polling-controller": "^12.0.2", + "@metamask/transaction-controller": "^45.0.0", + "@metamask/utils": "^11.1.0", + "ethereumjs-util": "^7.0.10", + "ethers": "^6.12.0" + }, + "devDependencies": { + "@metamask/auto-changelog": "^3.4.4", + "@metamask/eth-json-rpc-provider": "^4.1.8", + "@metamask/json-rpc-engine": "^10.0.3", + "@types/jest": "^27.4.1", + "deepmerge": "^4.2.2", + "jest": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "lodash": "^4.17.21", + "nock": "^13.3.1", + "ts-jest": "^27.1.4", + "typedoc": "^0.24.8", + "typedoc-plugin-missing-exports": "^2.0.0", + "typescript": "~5.2.2" + }, + "peerDependencies": { + "@metamask/network-controller": "^22.0.0", + "@metamask/transaction-controller": "^45.0.0" + }, + "engines": { + "node": "^18.18 || >=20" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/bridge-status-controller/tsconfig.build.json b/packages/bridge-status-controller/tsconfig.build.json new file mode 100644 index 0000000000..5d38b99686 --- /dev/null +++ b/packages/bridge-status-controller/tsconfig.build.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist", + "rootDir": "./src" + }, + "references": [ + { "path": "../accounts-controller/tsconfig.build.json" }, + { "path": "../approval-controller/tsconfig.build.json" }, + { "path": "../base-controller/tsconfig.build.json" }, + { "path": "../controller-utils/tsconfig.build.json" }, + { "path": "../keyring-controller/tsconfig.build.json" }, + { "path": "../network-controller/tsconfig.build.json" }, + { "path": "../preferences-controller/tsconfig.build.json" }, + { "path": "../polling-controller/tsconfig.build.json" } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/bridge-status-controller/tsconfig.json b/packages/bridge-status-controller/tsconfig.json new file mode 100644 index 0000000000..f989b1ba27 --- /dev/null +++ b/packages/bridge-status-controller/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./", + "resolveJsonModule": true + }, + "references": [ + { "path": "../accounts-controller" }, + { "path": "../base-controller" }, + { "path": "../controller-utils" }, + { "path": "../polling-controller" }, + { "path": "../network-controller" }, + { "path": "../transaction-controller" } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/bridge-status-controller/typedoc.json b/packages/bridge-status-controller/typedoc.json new file mode 100644 index 0000000000..c9da015dbf --- /dev/null +++ b/packages/bridge-status-controller/typedoc.json @@ -0,0 +1,7 @@ +{ + "entryPoints": ["./src/index.ts"], + "excludePrivate": true, + "hideGenerator": true, + "out": "docs", + "tsconfig": "./tsconfig.build.json" +} From 1d0daae263f48737c5cd10a244805d1f774839b7 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:15:22 -0500 Subject: [PATCH 03/49] chore: update readme --- packages/bridge-status-controller/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bridge-status-controller/README.md b/packages/bridge-status-controller/README.md index adb050aede..3c364ca057 100644 --- a/packages/bridge-status-controller/README.md +++ b/packages/bridge-status-controller/README.md @@ -1,14 +1,14 @@ -# `@metamask/bridge-controller` +# `@metamask/bridge-status-controller` -Manages bridge-related quote fetching functionality for MetaMask. +Manages bridge-related status fetching functionality for MetaMask. ## Installation -`yarn add @metamask/bridge-controller` +`yarn add @metamask/bridge-status-controller` or -`npm install @metamask/bridge-controller` +`npm install @metamask/bridge-status-controller` ## Contributing From dc655d07cd4e219f2ac9a6a4c6aa7867ae9f1870 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:15:51 -0500 Subject: [PATCH 04/49] chore: change version to 0.0.0 since it's not published --- packages/bridge-status-controller/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 2b643ed206..1b4ae17aed 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/bridge-status-controller", - "version": "1.0.0", + "version": "0.0.0", "description": "Manages bridge-related status fetching functionality for MetaMask", "keywords": [ "MetaMask", From 958225379cca662b62b42df6ba3d6d9b983ae014 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:16:21 -0500 Subject: [PATCH 05/49] chore: update yarn lock --- yarn.lock | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/yarn.lock b/yarn.lock index 500874473b..bb164366d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2615,6 +2615,39 @@ __metadata: languageName: unknown linkType: soft +"@metamask/bridge-status-controller@workspace:packages/bridge-status-controller": + version: 0.0.0-use.local + resolution: "@metamask/bridge-status-controller@workspace:packages/bridge-status-controller" + dependencies: + "@metamask/accounts-controller": "npm:^23.0.0" + "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/base-controller": "npm:^7.1.1" + "@metamask/controller-utils": "npm:^11.5.0" + "@metamask/eth-json-rpc-provider": "npm:^4.1.8" + "@metamask/json-rpc-engine": "npm:^10.0.3" + "@metamask/metamask-eth-abis": "npm:^3.1.1" + "@metamask/network-controller": "npm:^22.2.0" + "@metamask/polling-controller": "npm:^12.0.2" + "@metamask/transaction-controller": "npm:^45.0.0" + "@metamask/utils": "npm:^11.1.0" + "@types/jest": "npm:^27.4.1" + deepmerge: "npm:^4.2.2" + ethereumjs-util: "npm:^7.0.10" + ethers: "npm:^6.12.0" + jest: "npm:^27.5.1" + jest-environment-jsdom: "npm:^27.5.1" + lodash: "npm:^4.17.21" + nock: "npm:^13.3.1" + ts-jest: "npm:^27.1.4" + typedoc: "npm:^0.24.8" + typedoc-plugin-missing-exports: "npm:^2.0.0" + typescript: "npm:~5.2.2" + peerDependencies: + "@metamask/network-controller": ^22.0.0 + "@metamask/transaction-controller": ^45.0.0 + languageName: unknown + linkType: soft + "@metamask/browser-passworder@npm:^4.3.0": version: 4.3.0 resolution: "@metamask/browser-passworder@npm:4.3.0" From 524192b84ccb55948238604b36ba51db50115bd6 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:16:29 -0500 Subject: [PATCH 06/49] chore: add changelog --- packages/bridge-status-controller/CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 packages/bridge-status-controller/CHANGELOG.md diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md new file mode 100644 index 0000000000..8fcf72c699 --- /dev/null +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Initial release + +[Unreleased]: https://github.com/MetaMask/core/ From aba0200371448b6606e45b0dad2a6ca8c1f51793 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:16:41 -0500 Subject: [PATCH 07/49] chore: initial commit of code --- .../bridge-status-controller.test.ts | 497 ++++++++++++++++++ .../bridge-status-controller.ts | 376 +++++++++++++ .../bridge-status-controller/constants.ts | 13 + packages/bridge-status-controller/mocks.ts | 351 +++++++++++++ packages/bridge-status-controller/types.ts | 330 ++++++++++++ packages/bridge-status-controller/utils.ts | 82 +++ .../validators.test.ts | 295 +++++++++++ .../bridge-status-controller/validators.ts | 182 +++++++ 8 files changed, 2126 insertions(+) create mode 100644 packages/bridge-status-controller/bridge-status-controller.test.ts create mode 100644 packages/bridge-status-controller/bridge-status-controller.ts create mode 100644 packages/bridge-status-controller/constants.ts create mode 100644 packages/bridge-status-controller/mocks.ts create mode 100644 packages/bridge-status-controller/types.ts create mode 100644 packages/bridge-status-controller/utils.ts create mode 100644 packages/bridge-status-controller/validators.test.ts create mode 100644 packages/bridge-status-controller/validators.ts diff --git a/packages/bridge-status-controller/bridge-status-controller.test.ts b/packages/bridge-status-controller/bridge-status-controller.test.ts new file mode 100644 index 0000000000..aab6985511 --- /dev/null +++ b/packages/bridge-status-controller/bridge-status-controller.test.ts @@ -0,0 +1,497 @@ +import { flushPromises } from '../../../../test/lib/timer-helpers'; +import { Numeric } from '../../../../shared/modules/Numeric'; +import BridgeStatusController from './bridge-status-controller'; +import { BridgeStatusControllerMessenger } from './types'; +import { DEFAULT_BRIDGE_STATUS_STATE } from './constants'; +import * as bridgeStatusUtils from './utils'; +import { + MockStatusResponse, + MockTxHistory, + getMockStartPollingForBridgeTxStatusArgs, +} from './mocks'; + +const EMPTY_INIT_STATE = { + bridgeStatusState: { ...DEFAULT_BRIDGE_STATUS_STATE }, +}; + +const getMessengerMock = ({ + account = '0xaccount1', + srcChainId = 42161, +} = {}) => + ({ + call: jest.fn((method: string) => { + if (method === 'AccountsController:getSelectedAccount') { + return { address: account }; + } else if (method === 'NetworkController:findNetworkClientIdByChainId') { + return 'networkClientId'; + } else if (method === 'NetworkController:getState') { + return { selectedNetworkClientId: 'networkClientId' }; + } else if (method === 'NetworkController:getNetworkClientById') { + return { + configuration: { + chainId: new Numeric(srcChainId, 10).toPrefixedHexString(), + }, + }; + } + return null; + }), + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as unknown as jest.Mocked); + +const executePollingWithPendingStatus = async () => { + // Setup + jest.useFakeTimers(); + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + }); + const startPollingSpy = jest.spyOn(bridgeStatusController, 'startPolling'); + const fetchBridgeTxStatusSpy = jest.spyOn( + bridgeStatusUtils, + 'fetchBridgeTxStatus', + ); + + // Execution + await bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + fetchBridgeTxStatusSpy.mockImplementationOnce(async () => { + return MockStatusResponse.getPending(); + }); + jest.advanceTimersByTime(10000); + await flushPromises(); + + return { + bridgeStatusController, + startPollingSpy, + fetchBridgeTxStatusSpy, + }; +}; + +describe('BridgeStatusController', () => { + describe('constructor', () => { + it('should setup correctly', () => { + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + }); + expect(bridgeStatusController.state).toEqual(EMPTY_INIT_STATE); + }); + it('rehydrates the tx history state', async () => { + // Setup + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + state: { + bridgeStatusState: { + txHistory: MockTxHistory.getPending(), + }, + }, + }); + + // Execution + await bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + + // Assertion + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toMatchSnapshot(); + }); + it('restarts polling for history items that are not complete', async () => { + // Setup + jest.useFakeTimers(); + const fetchBridgeTxStatusSpy = jest.spyOn( + bridgeStatusUtils, + 'fetchBridgeTxStatus', + ); + + // Execution + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + state: { + bridgeStatusState: { + txHistory: MockTxHistory.getPending(), + }, + }, + }); + jest.advanceTimersByTime(10000); + await flushPromises(); + + // Assertions + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + }); + }); + describe('startPollingForBridgeTxStatus', () => { + it('sets the inital tx history state', async () => { + // Setup + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + }); + + // Execution + await bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + + // Assertion + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toMatchSnapshot(); + }); + it('starts polling and updates the tx history when the status response is received', async () => { + const { + bridgeStatusController, + startPollingSpy, + fetchBridgeTxStatusSpy, + } = await executePollingWithPendingStatus(); + + // Assertions + expect(startPollingSpy).toHaveBeenCalledTimes(1); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalled(); + expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( + MockTxHistory.getPending(), + ); + }); + it('stops polling when the status response is complete', async () => { + // Setup + jest.useFakeTimers(); + jest + .spyOn(Date, 'now') + .mockImplementation( + () => + MockTxHistory.getComplete().bridgeTxMetaId1.completionTime ?? 10, + ); + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + }); + const fetchBridgeTxStatusSpy = jest.spyOn( + bridgeStatusUtils, + 'fetchBridgeTxStatus', + ); + const stopPollingByNetworkClientIdSpy = jest.spyOn( + bridgeStatusController, + 'stopPollingByPollingToken', + ); + + // Execution + await bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + fetchBridgeTxStatusSpy.mockImplementationOnce(async () => { + return MockStatusResponse.getComplete(); + }); + jest.advanceTimersByTime(10000); + await flushPromises(); + + // Assertions + expect(stopPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1); + expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( + MockTxHistory.getComplete(), + ); + + jest.restoreAllMocks(); + }); + }); + describe('resetState', () => { + it('resets the state', async () => { + const { bridgeStatusController } = + await executePollingWithPendingStatus(); + + expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( + MockTxHistory.getPending(), + ); + bridgeStatusController.resetState(); + expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( + EMPTY_INIT_STATE.bridgeStatusState.txHistory, + ); + }); + }); + describe('wipeBridgeStatus', () => { + it('wipes the bridge status for the given address', async () => { + // Setup + jest.useFakeTimers(); + + let getSelectedAccountCalledTimes = 0; + const messengerMock = { + call: jest.fn((method: string) => { + if (method === 'AccountsController:getSelectedAccount') { + let account; + if (getSelectedAccountCalledTimes === 0) { + account = '0xaccount1'; + } else { + account = '0xaccount2'; + } + getSelectedAccountCalledTimes += 1; + return { address: account }; + } else if ( + method === 'NetworkController:findNetworkClientIdByChainId' + ) { + return 'networkClientId'; + } else if (method === 'NetworkController:getState') { + return { selectedNetworkClientId: 'networkClientId' }; + } else if (method === 'NetworkController:getNetworkClientById') { + return { + configuration: { + chainId: new Numeric(42161, 10).toPrefixedHexString(), + }, + }; + } + return null; + }), + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as unknown as jest.Mocked; + const bridgeStatusController = new BridgeStatusController({ + messenger: messengerMock, + }); + const fetchBridgeTxStatusSpy = jest + .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete(); + }) + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete({ + srcTxHash: '0xsrcTxHash2', + destTxHash: '0xdestTxHash2', + }); + }); + + // Start polling for 0xaccount1 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + + // Start polling for 0xaccount2 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs({ + txMetaId: 'bridgeTxMetaId2', + srcTxHash: '0xsrcTxHash2', + account: '0xaccount2', + }), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(2); + + // Check that both accounts have a tx history entry + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toHaveProperty('bridgeTxMetaId1'); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toHaveProperty('bridgeTxMetaId2'); + + // Wipe the status for 1 account only + bridgeStatusController.wipeBridgeStatus({ + address: '0xaccount1', + ignoreNetwork: false, + }); + + // Assertions + const txHistoryItems = Object.values( + bridgeStatusController.state.bridgeStatusState.txHistory, + ); + expect(txHistoryItems).toHaveLength(1); + expect(txHistoryItems[0].account).toEqual('0xaccount2'); + }); + it('wipes the bridge status for all networks if ignoreNetwork is true', () => { + // Setup + jest.useFakeTimers(); + const messengerMock = { + call: jest.fn((method: string) => { + if (method === 'AccountsController:getSelectedAccount') { + return { address: '0xaccount1' }; + } else if ( + method === 'NetworkController:findNetworkClientIdByChainId' + ) { + return 'networkClientId'; + } else if (method === 'NetworkController:getState') { + return { selectedNetworkClientId: 'networkClientId' }; + } else if (method === 'NetworkController:getNetworkClientById') { + return { + configuration: { + chainId: new Numeric(42161, 10).toPrefixedHexString(), + }, + }; + } + return null; + }), + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as unknown as jest.Mocked; + const bridgeStatusController = new BridgeStatusController({ + messenger: messengerMock, + }); + const fetchBridgeTxStatusSpy = jest + .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete(); + }) + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete({ + srcTxHash: '0xsrcTxHash2', + }); + }); + + // Start polling for chainId 42161 to chainId 1 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs({ + account: '0xaccount1', + srcTxHash: '0xsrcTxHash1', + txMetaId: 'bridgeTxMetaId1', + srcChainId: 42161, + destChainId: 1, + }), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + + // Start polling for chainId 10 to chainId 123 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs({ + account: '0xaccount1', + srcTxHash: '0xsrcTxHash2', + txMetaId: 'bridgeTxMetaId2', + srcChainId: 10, + destChainId: 123, + }), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(2); + + // Check we have a tx history entry for each chainId + expect( + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 + .quote.srcChainId, + ).toEqual(42161); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 + .quote.destChainId, + ).toEqual(1); + + expect( + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 + .quote.srcChainId, + ).toEqual(10); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 + .quote.destChainId, + ).toEqual(123); + + bridgeStatusController.wipeBridgeStatus({ + address: '0xaccount1', + ignoreNetwork: true, + }); + + // Assertions + const txHistoryItems = Object.values( + bridgeStatusController.state.bridgeStatusState.txHistory, + ); + expect(txHistoryItems).toHaveLength(0); + }); + it('wipes the bridge status only for the current network if ignoreNetwork is false', () => { + // Setup + jest.useFakeTimers(); + const messengerMock = { + call: jest.fn((method: string) => { + if (method === 'AccountsController:getSelectedAccount') { + return { address: '0xaccount1' }; + } else if ( + method === 'NetworkController:findNetworkClientIdByChainId' + ) { + return 'networkClientId'; + } else if (method === 'NetworkController:getState') { + return { selectedNetworkClientId: 'networkClientId' }; + } else if (method === 'NetworkController:getNetworkClientById') { + return { + configuration: { + // This is what controls the selectedNetwork and what gets wiped in this test + chainId: new Numeric(42161, 10).toPrefixedHexString(), + }, + }; + } + return null; + }), + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as unknown as jest.Mocked; + const bridgeStatusController = new BridgeStatusController({ + messenger: messengerMock, + }); + const fetchBridgeTxStatusSpy = jest + .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete(); + }) + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete({ + srcTxHash: '0xsrcTxHash2', + }); + }); + + // Start polling for chainId 42161 to chainId 1 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs({ + account: '0xaccount1', + srcTxHash: '0xsrcTxHash1', + txMetaId: 'bridgeTxMetaId1', + srcChainId: 42161, + destChainId: 1, + }), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + + // Start polling for chainId 10 to chainId 123 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs({ + account: '0xaccount1', + srcTxHash: '0xsrcTxHash2', + txMetaId: 'bridgeTxMetaId2', + srcChainId: 10, + destChainId: 123, + }), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(2); + + // Check we have a tx history entry for each chainId + expect( + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 + .quote.srcChainId, + ).toEqual(42161); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 + .quote.destChainId, + ).toEqual(1); + + expect( + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 + .quote.srcChainId, + ).toEqual(10); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 + .quote.destChainId, + ).toEqual(123); + + bridgeStatusController.wipeBridgeStatus({ + address: '0xaccount1', + ignoreNetwork: false, + }); + + // Assertions + const txHistoryItems = Object.values( + bridgeStatusController.state.bridgeStatusState.txHistory, + ); + expect(txHistoryItems).toHaveLength(1); + expect(txHistoryItems[0].quote.srcChainId).toEqual(10); + expect(txHistoryItems[0].quote.destChainId).toEqual(123); + }); + }); +}); diff --git a/packages/bridge-status-controller/bridge-status-controller.ts b/packages/bridge-status-controller/bridge-status-controller.ts new file mode 100644 index 0000000000..3c278b1889 --- /dev/null +++ b/packages/bridge-status-controller/bridge-status-controller.ts @@ -0,0 +1,376 @@ +import { StateMetadata } from '@metamask/base-controller'; +import { StaticIntervalPollingController } from '@metamask/polling-controller'; +import { Hex } from '@metamask/utils'; +// eslint-disable-next-line import/no-restricted-paths +import { + StatusTypes, + BridgeStatusControllerState, + StartPollingForBridgeTxStatusArgsSerialized, + BridgeStatusState, +} from '../../../../shared/types/bridge-status'; +import { decimalToPrefixedHex } from '../../../../shared/modules/conversion.utils'; +import { + BRIDGE_STATUS_CONTROLLER_NAME, + DEFAULT_BRIDGE_STATUS_STATE, + REFRESH_INTERVAL_MS, +} from './constants'; +import { BridgeStatusControllerMessenger } from './types'; +import { fetchBridgeTxStatus, getStatusRequestWithSrcTxHash } from './utils'; + +const metadata: StateMetadata = { + // We want to persist the bridge status state so that we can show the proper data for the Activity list + // basically match the behavior of TransactionController + bridgeStatusState: { + persist: true, + anonymous: false, + }, +}; + +/** The input to start polling for the {@link BridgeStatusController} */ +type BridgeStatusPollingInput = FetchBridgeTxStatusArgs; + +type SrcTxMetaId = string; +export type FetchBridgeTxStatusArgs = { + bridgeTxMetaId: string; +}; +export default class BridgeStatusController extends StaticIntervalPollingController()< + typeof BRIDGE_STATUS_CONTROLLER_NAME, + BridgeStatusControllerState, + BridgeStatusControllerMessenger +> { + #pollingTokensByTxMetaId: Record = {}; + + constructor({ + messenger, + state, + }: { + messenger: BridgeStatusControllerMessenger; + state?: { bridgeStatusState?: Partial }; + }) { + super({ + name: BRIDGE_STATUS_CONTROLLER_NAME, + metadata, + messenger, + // Restore the persisted state + state: { + ...state, + bridgeStatusState: { + ...DEFAULT_BRIDGE_STATUS_STATE, + ...state?.bridgeStatusState, + }, + }, + }); + + // Register action handlers + this.messagingSystem.registerActionHandler( + `${BRIDGE_STATUS_CONTROLLER_NAME}:startPollingForBridgeTxStatus`, + this.startPollingForBridgeTxStatus.bind(this), + ); + this.messagingSystem.registerActionHandler( + `${BRIDGE_STATUS_CONTROLLER_NAME}:wipeBridgeStatus`, + this.wipeBridgeStatus.bind(this), + ); + + // Set interval + this.setIntervalLength(REFRESH_INTERVAL_MS); + + // If you close the extension, but keep the browser open, the polling continues + // If you close the browser, the polling stops + // Check for historyItems that do not have a status of complete and restart polling + this.#restartPollingForIncompleteHistoryItems(); + } + + resetState = () => { + this.update((_state) => { + _state.bridgeStatusState = { + ...DEFAULT_BRIDGE_STATUS_STATE, + }; + }); + }; + + wipeBridgeStatus = ({ + address, + ignoreNetwork, + }: { + address: string; + ignoreNetwork: boolean; + }) => { + // Wipe all networks for this address + if (ignoreNetwork) { + this.update((_state) => { + _state.bridgeStatusState = { + ...DEFAULT_BRIDGE_STATUS_STATE, + }; + }); + } else { + const { selectedNetworkClientId } = this.messagingSystem.call( + 'NetworkController:getState', + ); + const selectedNetworkClient = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + selectedNetworkClientId, + ); + const selectedChainId = selectedNetworkClient.configuration.chainId; + + this.#wipeBridgeStatusByChainId(address, selectedChainId); + } + }; + + #restartPollingForIncompleteHistoryItems = () => { + // Check for historyItems that do not have a status of complete and restart polling + const { bridgeStatusState } = this.state; + const historyItems = Object.values(bridgeStatusState.txHistory); + const incompleteHistoryItems = historyItems + .filter( + (historyItem) => + historyItem.status.status === StatusTypes.PENDING || + historyItem.status.status === StatusTypes.UNKNOWN, + ) + .filter((historyItem) => { + // Check if we are already polling this tx, if so, skip restarting polling for that + const srcTxMetaId = historyItem.txMetaId; + const pollingToken = this.#pollingTokensByTxMetaId[srcTxMetaId]; + return !pollingToken; + }); + + incompleteHistoryItems.forEach((historyItem) => { + const bridgeTxMetaId = historyItem.txMetaId; + + // We manually call startPolling() here rather than go through startPollingForBridgeTxStatus() + // because we don't want to overwrite the existing historyItem in state + this.#pollingTokensByTxMetaId[bridgeTxMetaId] = this.startPolling({ + bridgeTxMetaId, + }); + }); + }; + + startPollingForBridgeTxStatus = ( + startPollingForBridgeTxStatusArgs: StartPollingForBridgeTxStatusArgsSerialized, + ) => { + const { + bridgeTxMeta, + statusRequest, + quoteResponse, + startTime, + slippagePercentage, + initialDestAssetBalance, + targetContractAddress, + } = startPollingForBridgeTxStatusArgs; + const { bridgeStatusState } = this.state; + const { address: account } = this.#getSelectedAccount(); + + // Write all non-status fields to state so we can reference the quote in Activity list without the Bridge API + // We know it's in progress but not the exact status yet + const txHistoryItem = { + txMetaId: bridgeTxMeta.id, + quote: quoteResponse.quote, + startTime, + estimatedProcessingTimeInSeconds: + quoteResponse.estimatedProcessingTimeInSeconds, + slippagePercentage, + pricingData: { + amountSent: quoteResponse.sentAmount.amount, + amountSentInUsd: quoteResponse.sentAmount.usd ?? undefined, + quotedGasInUsd: quoteResponse.gasFee.usd ?? undefined, + quotedReturnInUsd: quoteResponse.toTokenAmount.usd ?? undefined, + }, + initialDestAssetBalance, + targetContractAddress, + account, + status: { + // We always have a PENDING status when we start polling for a tx, don't need the Bridge API for that + // Also we know the bare minimum fields for status at this point in time + status: StatusTypes.PENDING, + srcChain: { + chainId: statusRequest.srcChainId, + txHash: statusRequest.srcTxHash, + }, + }, + hasApprovalTx: Boolean(quoteResponse.approval), + }; + this.update((_state) => { + _state.bridgeStatusState = { + ...bridgeStatusState, + txHistory: { + ...bridgeStatusState.txHistory, + // Use the txMeta.id as the key so we can reference the txMeta in TransactionController + [bridgeTxMeta.id]: txHistoryItem, + }, + }; + }); + + this.#pollingTokensByTxMetaId[bridgeTxMeta.id] = this.startPolling({ + bridgeTxMetaId: bridgeTxMeta.id, + }); + }; + + // This will be called after you call this.startPolling() + // The args passed in are the args you passed in to startPolling() + _executePoll = async (pollingInput: BridgeStatusPollingInput) => { + await this.#fetchBridgeTxStatus(pollingInput); + }; + + #getSelectedAccount() { + return this.messagingSystem.call('AccountsController:getSelectedAccount'); + } + + #fetchBridgeTxStatus = async ({ + bridgeTxMetaId, + }: FetchBridgeTxStatusArgs) => { + const { bridgeStatusState } = this.state; + + try { + // We try here because we receive 500 errors from Bridge API if we try to fetch immediately after submitting the source tx + // Oddly mostly happens on Optimism, never on Arbitrum. By the 2nd fetch, the Bridge API responds properly. + // Also srcTxHash may not be available immediately for STX, so we don't want to fetch in those cases + const historyItem = bridgeStatusState.txHistory[bridgeTxMetaId]; + const srcTxHash = this.#getSrcTxHash(bridgeTxMetaId); + if (!srcTxHash) { + return; + } + + this.#updateSrcTxHash(bridgeTxMetaId, srcTxHash); + + const statusRequest = getStatusRequestWithSrcTxHash( + historyItem.quote, + srcTxHash, + ); + const status = await fetchBridgeTxStatus(statusRequest); + const newBridgeHistoryItem = { + ...historyItem, + status, + completionTime: + status.status === StatusTypes.COMPLETE || + status.status === StatusTypes.FAILED + ? Date.now() + : undefined, // TODO make this more accurate by looking up dest txHash block time + }; + + // No need to purge these on network change or account change, TransactionController does not purge either. + // TODO In theory we can skip checking status if it's not the current account/network + // we need to keep track of the account that this is associated with as well so that we don't show it in Activity list for other accounts + // First stab at this will not stop polling when you are on a different account + this.update((_state) => { + _state.bridgeStatusState = { + ...bridgeStatusState, + txHistory: { + ...bridgeStatusState.txHistory, + [bridgeTxMetaId]: newBridgeHistoryItem, + }, + }; + }); + + const pollingToken = this.#pollingTokensByTxMetaId[bridgeTxMetaId]; + + if ( + (status.status === StatusTypes.COMPLETE || + status.status === StatusTypes.FAILED) && + pollingToken + ) { + this.stopPollingByPollingToken(pollingToken); + + if (status.status === StatusTypes.COMPLETE) { + this.messagingSystem.publish( + `${BRIDGE_STATUS_CONTROLLER_NAME}:bridgeTransactionComplete`, + { bridgeHistoryItem: newBridgeHistoryItem }, + ); + } + if (status.status === StatusTypes.FAILED) { + this.messagingSystem.publish( + `${BRIDGE_STATUS_CONTROLLER_NAME}:bridgeTransactionFailed`, + { bridgeHistoryItem: newBridgeHistoryItem }, + ); + } + } + } catch (e) { + console.log('Failed to fetch bridge tx status', e); + } + }; + + #getSrcTxHash = (bridgeTxMetaId: string): string | undefined => { + const { bridgeStatusState } = this.state; + // Prefer the srcTxHash from bridgeStatusState so we don't have to l ook up in TransactionController + // But it is possible to have bridgeHistoryItem in state without the srcTxHash yet when it is an STX + const srcTxHash = + bridgeStatusState.txHistory[bridgeTxMetaId].status.srcChain.txHash; + + if (srcTxHash) { + return srcTxHash; + } + + // Look up in TransactionController if txMeta has been updated with the srcTxHash + const txControllerState = this.messagingSystem.call( + 'TransactionController:getState', + ); + const txMeta = txControllerState.transactions.find( + (tx) => tx.id === bridgeTxMetaId, + ); + return txMeta?.hash; + }; + + #updateSrcTxHash = (bridgeTxMetaId: string, srcTxHash: string) => { + const { bridgeStatusState } = this.state; + if (bridgeStatusState.txHistory[bridgeTxMetaId].status.srcChain.txHash) { + return; + } + + this.update((_state) => { + _state.bridgeStatusState = { + ...bridgeStatusState, + txHistory: { + ...bridgeStatusState.txHistory, + [bridgeTxMetaId]: { + ...bridgeStatusState.txHistory[bridgeTxMetaId], + status: { + ...bridgeStatusState.txHistory[bridgeTxMetaId].status, + srcChain: { + ...bridgeStatusState.txHistory[bridgeTxMetaId].status.srcChain, + txHash: srcTxHash, + }, + }, + }, + }, + }; + }); + }; + + // Wipes the bridge status for the given address and chainId + // Will match only source chainId to the selectedChainId + #wipeBridgeStatusByChainId = (address: string, selectedChainId: Hex) => { + const sourceTxMetaIdsToDelete = Object.keys( + this.state.bridgeStatusState.txHistory, + ).filter((txMetaId) => { + const bridgeHistoryItem = + this.state.bridgeStatusState.txHistory[txMetaId]; + + const hexSourceChainId = decimalToPrefixedHex( + bridgeHistoryItem.quote.srcChainId, + ); + + return ( + bridgeHistoryItem.account === address && + hexSourceChainId === selectedChainId + ); + }); + + sourceTxMetaIdsToDelete.forEach((sourceTxMetaId) => { + const pollingToken = this.#pollingTokensByTxMetaId[sourceTxMetaId]; + + if (pollingToken) { + this.stopPollingByPollingToken( + this.#pollingTokensByTxMetaId[sourceTxMetaId], + ); + } + }); + + this.update((_state) => { + _state.bridgeStatusState.txHistory = sourceTxMetaIdsToDelete.reduce( + (acc, sourceTxMetaId) => { + delete acc[sourceTxMetaId]; + return acc; + }, + _state.bridgeStatusState.txHistory, + ); + }); + }; +} diff --git a/packages/bridge-status-controller/constants.ts b/packages/bridge-status-controller/constants.ts new file mode 100644 index 0000000000..da17f195c2 --- /dev/null +++ b/packages/bridge-status-controller/constants.ts @@ -0,0 +1,13 @@ +import { BridgeStatusState } from '../../../../shared/types/bridge-status'; + +export const REFRESH_INTERVAL_MS = 10 * 1000; + +export const BRIDGE_STATUS_CONTROLLER_NAME = 'BridgeStatusController'; + +export const DEFAULT_BRIDGE_STATUS_STATE: BridgeStatusState = { + txHistory: {}, +}; + +export const DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE = { + bridgeStatusState: { ...DEFAULT_BRIDGE_STATUS_STATE }, +}; diff --git a/packages/bridge-status-controller/mocks.ts b/packages/bridge-status-controller/mocks.ts new file mode 100644 index 0000000000..80c3cf6cdf --- /dev/null +++ b/packages/bridge-status-controller/mocks.ts @@ -0,0 +1,351 @@ +import { TransactionMeta } from '@metamask/transaction-controller'; +import { + BridgeId, + StatusResponse, + StatusTypes, + ActionTypes, + StartPollingForBridgeTxStatusArgsSerialized, + BridgeHistoryItem, +} from '../../../../shared/types/bridge-status'; + +export const MockStatusResponse = { + getPending: ({ + srcTxHash = '0xsrcTxHash1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + status: 'PENDING' as StatusTypes, + srcChain: { + chainId: srcChainId, + txHash: srcTxHash, + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2518.47', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: destChainId, + token: {}, + }, + }), + getComplete: ({ + srcTxHash = '0xsrcTxHash1', + destTxHash = '0xdestTxHash1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + status: 'COMPLETE' as StatusTypes, + isExpectedToken: true, + bridge: 'across' as BridgeId, + srcChain: { + chainId: srcChainId, + txHash: srcTxHash, + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: destChainId, + txHash: destTxHash, + amount: '990654755978611', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }), +}; + +let mockFetchBridgeTxStatusCount = 0; +export const mockFetchBridgeTxStatus: () => Promise = () => { + return new Promise((resolve) => { + console.log('HELLO mockFetchBridgeTxStatus', mockFetchBridgeTxStatusCount); + setTimeout(() => { + if (mockFetchBridgeTxStatusCount === 0) { + resolve( + MockStatusResponse.getPending({ + srcTxHash: + '0xe3e223b9725765a7de557effdb2b507ace3534bcff2c1fe3a857e0791e56a518', + srcChainId: 1, + destChainId: 42161, + }), + ); + } else { + resolve( + MockStatusResponse.getComplete({ + srcTxHash: + '0xe3e223b9725765a7de557effdb2b507ace3534bcff2c1fe3a857e0791e56a518', + destTxHash: + '0x010e1bffe8288956012e6b6132d7eb3eaf9d0bbf066bd13aae13b973c678508f', + srcChainId: 1, + destChainId: 42161, + }), + ); + } + mockFetchBridgeTxStatusCount += 1; + }, 2000); + }); +}; + +export const getMockQuote = ({ + srcChainId = 42161, + destChainId = 10, +} = {}) => ({ + requestId: '197c402f-cb96-4096-9f8c-54aed84ca776', + srcChainId, + srcTokenAmount: '991250000000000', + srcAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + destChainId, + destTokenAmount: '990654755978612', + destAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + feeData: { + metabridge: { + amount: '8750000000000', + asset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }, + bridgeId: 'lifi', + bridges: ['across'], + steps: [ + { + action: 'bridge' as ActionTypes, + srcChainId, + destChainId, + protocol: { + name: 'across', + displayName: 'Across', + icon: 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png', + }, + srcAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + destAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + srcAmount: '991250000000000', + destAmount: '990654755978612', + }, + ], +}); + +export const getMockStartPollingForBridgeTxStatusArgs = ({ + txMetaId = 'bridgeTxMetaId1', + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, +} = {}): StartPollingForBridgeTxStatusArgsSerialized => ({ + bridgeTxMeta: { + id: txMetaId, + } as TransactionMeta, + statusRequest: { + bridgeId: 'lifi', + srcTxHash, + bridge: 'across', + srcChainId, + destChainId, + quote: getMockQuote({ srcChainId, destChainId }), + refuel: false, + }, + quoteResponse: { + quote: getMockQuote({ srcChainId, destChainId }), + trade: { + chainId: srcChainId, + to: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + from: account, + value: '0x038d7ea4c68000', + data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038589602234000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000007f544a44c0000000000000000000000000056ca675c3633cc16bd6849e2b431d4e8de5e23bf000000000000000000000000000000000000000000000000000000000000006c5a39b10a4f4f0747826140d2c5fe6ef47965741f6f7a4734bf784bf3ae3f24520000000a000222266cc2dca0671d2a17ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b7100000000000000000000000000000000000000009ce3c510b3f58edc8d53ae708056e30926f62d0b42d5c9b61c391bb4e8a2c1917f8ed995169ffad0d79af2590303e83c57e15a9e0b248679849556c2e03a1c811b', + gasLimit: 282915, + }, + approval: null, + estimatedProcessingTimeInSeconds: 15, + sentAmount: { amount: '1.234', valueInCurrency: null, usd: null }, + toTokenAmount: { amount: '1.234', valueInCurrency: null, usd: null }, + totalNetworkFee: { amount: '1.234', valueInCurrency: null, usd: null }, + totalMaxNetworkFee: { amount: '1.234', valueInCurrency: null, usd: null }, + gasFee: { amount: '1.234', valueInCurrency: null, usd: null }, + adjustedReturn: { valueInCurrency: null, usd: null }, + swapRate: '1.234', + cost: { valueInCurrency: null, usd: null }, + }, + startTime: 1729964825189, + slippagePercentage: 0, + initialDestAssetBalance: undefined, + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', +}); + +export const MockTxHistory = { + getInitNoSrcTxHash: ({ + txMetaId = 'bridgeTxMetaId1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}): Record => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { amountSent: '1.234' }, + status: MockStatusResponse.getPending({ + srcChainId, + }), + hasApprovalTx: false, + }, + }), + getInit: ({ + txMetaId = 'bridgeTxMetaId1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}): Record => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { amountSent: '1.234' }, + status: MockStatusResponse.getPending({ + srcChainId, + }), + hasApprovalTx: false, + }, + }), + getPending: ({ + txMetaId = 'bridgeTxMetaId1', + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}): Record => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + status: MockStatusResponse.getPending({ + srcTxHash, + srcChainId, + }), + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { amountSent: '1.234' }, + hasApprovalTx: false, + }, + }), + getComplete: ({ + txMetaId = 'bridgeTxMetaId1', + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}): Record => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + completionTime: 1736277625746, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + status: MockStatusResponse.getComplete({ srcTxHash }), + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { + amountSent: '1.234', + amountSentInUsd: undefined, + quotedGasInUsd: undefined, + quotedReturnInUsd: undefined, + }, + hasApprovalTx: false, + }, + }), +}; diff --git a/packages/bridge-status-controller/types.ts b/packages/bridge-status-controller/types.ts new file mode 100644 index 0000000000..4045fc1183 --- /dev/null +++ b/packages/bridge-status-controller/types.ts @@ -0,0 +1,330 @@ +import type { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller'; +import type { + ControllerGetStateAction, + ControllerStateChangeEvent, + RestrictedMessenger, +} from '@metamask/base-controller'; +import type { + NetworkControllerFindNetworkClientIdByChainIdAction, + NetworkControllerGetNetworkClientByIdAction, + NetworkControllerGetStateAction, +} from '@metamask/network-controller'; +import type { TransactionControllerGetStateAction } from '@metamask/transaction-controller'; +import type { + TransactionControllerState, + TransactionMeta, +} from '@metamask/transaction-controller'; + +import type { ChainId, Quote, QuoteMetadata, QuoteResponse } from './bridge'; +import type BridgeStatusController from './bridge-status-controller'; +import type { BRIDGE_STATUS_CONTROLLER_NAME } from './constants'; +import type { + BridgeHistoryItem, + BridgeStatusAction, + BridgeStatusControllerState, +} from '../../../../shared/types/bridge-status'; +import type { SmartTransactionsMetaMaskState } from '../modules/selectors'; +import type { + NetworkState, + ProviderConfigState, +} from '../modules/selectors/networks'; + +// All fields need to be types not interfaces, same with their children fields +// o/w you get a type error + +export enum StatusTypes { + UNKNOWN = 'UNKNOWN', + FAILED = 'FAILED', + PENDING = 'PENDING', + COMPLETE = 'COMPLETE', +} + +export type StatusRequest = { + bridgeId: string; // lifi, socket, squid + srcTxHash?: string; // lifi, socket, squid, might be undefined for STX + bridge: string; // lifi, socket, squid + srcChainId: ChainId; // lifi, socket, squid + destChainId: ChainId; // lifi, socket, squid + quote?: Quote; // squid + refuel?: boolean; // lifi +}; + +export type StatusRequestDto = Omit< + StatusRequest, + 'quote' | 'srcChainId' | 'destChainId' | 'refuel' +> & { + srcChainId: string; // lifi, socket, squid + destChainId: string; // lifi, socket, squid + requestId?: string; + refuel?: string; // lifi +}; + +export type StatusRequestWithSrcTxHash = StatusRequest & { + srcTxHash: string; +}; + +export type Asset = { + chainId: ChainId; + address: string; + symbol: string; + name: string; + decimals: number; + icon?: string | null; +}; + +export type SrcChainStatus = { + chainId: ChainId; + /** + * The txHash of the transaction on the source chain. + * This might be undefined for smart transactions (STX) + */ + txHash?: string; + /** + * The atomic amount of the token sent minus fees on the source chain + */ + amount?: string; + token?: Record | Asset; +}; + +export type DestChainStatus = { + chainId: ChainId; + txHash?: string; + /** + * The atomic amount of the token received on the destination chain + */ + amount?: string; + token?: Record | Asset; +}; + +export enum BridgeId { + HOP = 'hop', + CELER = 'celer', + CELERCIRCLE = 'celercircle', + CONNEXT = 'connext', + POLYGON = 'polygon', + AVALANCHE = 'avalanche', + MULTICHAIN = 'multichain', + AXELAR = 'axelar', + ACROSS = 'across', + STARGATE = 'stargate', +} + +export enum FeeType { + METABRIDGE = 'metabridge', + REFUEL = 'refuel', +} + +export type FeeData = { + amount: string; + asset: Asset; +}; + +export type Protocol = { + displayName?: string; + icon?: string; + name?: string; // for legacy quotes +}; + +export enum ActionTypes { + BRIDGE = 'bridge', + SWAP = 'swap', + REFUEL = 'refuel', +} + +export type Step = { + action: ActionTypes; + srcChainId: ChainId; + destChainId?: ChainId; + srcAsset: Asset; + destAsset: Asset; + srcAmount: string; + destAmount: string; + protocol: Protocol; +}; + +export type StatusResponse = { + status: StatusTypes; + srcChain: SrcChainStatus; + destChain?: DestChainStatus; + bridge?: BridgeId; + isExpectedToken?: boolean; + isUnrecognizedRouterAddress?: boolean; + refuel?: RefuelStatusResponse; +}; + +export type RefuelStatusResponse = object & StatusResponse; + +export type RefuelData = object & Step; + +export type BridgeHistoryItem = { + txMetaId: string; // Need this to handle STX that might not have a txHash immediately + quote: Quote; + status: StatusResponse; + startTime?: number; // timestamp in ms + estimatedProcessingTimeInSeconds: number; + slippagePercentage: number; + completionTime?: number; // timestamp in ms + pricingData?: { + /** + * From QuoteMetadata.sentAmount.amount, the actual amount sent by user in non-atomic decimal form + */ + amountSent: string; + amountSentInUsd?: string; + quotedGasInUsd?: string; // from QuoteMetadata.gasFee.usd + quotedReturnInUsd?: string; // from QuoteMetadata.toTokenAmount.usd + quotedRefuelSrcAmountInUsd?: string; + quotedRefuelDestAmountInUsd?: string; + }; + initialDestAssetBalance?: string; + targetContractAddress?: string; + account: string; + hasApprovalTx: boolean; +}; + +export enum BridgeStatusAction { + START_POLLING_FOR_BRIDGE_TX_STATUS = 'startPollingForBridgeTxStatus', + WIPE_BRIDGE_STATUS = 'wipeBridgeStatus', + GET_STATE = 'getState', +} + +export type TokenAmountValuesSerialized = { + amount: string; + valueInCurrency: string | null; + usd: string | null; +}; + +export type QuoteMetadataSerialized = { + gasFee: TokenAmountValuesSerialized; + /** + * The total network fee for the bridge transaction + * estimatedGasFees + relayerFees + */ + totalNetworkFee: TokenAmountValuesSerialized; + /** + * The total max network fee for the bridge transaction + * maxGasFees + relayerFees + */ + totalMaxNetworkFee: TokenAmountValuesSerialized; + toTokenAmount: TokenAmountValuesSerialized; + /** + * The adjusted return for the bridge transaction + * destTokenAmount - totalNetworkFee + */ + adjustedReturn: Omit; + /** + * The actual amount sent by user in non-atomic decimal form + * srcTokenAmount + metabridgeFee + */ + sentAmount: TokenAmountValuesSerialized; + swapRate: string; // destTokenAmount / sentAmount + /** + * The cost of the bridge transaction + * sentAmount - adjustedReturn + */ + cost: Omit; +}; + +export type StartPollingForBridgeTxStatusArgs = { + bridgeTxMeta: TransactionMeta; + statusRequest: StatusRequest; + quoteResponse: QuoteResponse & QuoteMetadata; + startTime?: BridgeHistoryItem['startTime']; + slippagePercentage: BridgeHistoryItem['slippagePercentage']; + initialDestAssetBalance?: BridgeHistoryItem['initialDestAssetBalance']; + targetContractAddress?: BridgeHistoryItem['targetContractAddress']; +}; + +/** + * Chrome: The BigNumber values are automatically serialized to strings when sent to the background + * Firefox: The BigNumber values are not serialized to strings when sent to the background, + * so we force the ui to do it manually, by using StartPollingForBridgeTxStatusArgsSerialized type on the startPollingForBridgeTxStatus action + */ +export type StartPollingForBridgeTxStatusArgsSerialized = Omit< + StartPollingForBridgeTxStatusArgs, + 'quoteResponse' +> & { + quoteResponse: QuoteResponse & QuoteMetadataSerialized; +}; + +export type SourceChainTxMetaId = string; + +export type BridgeStatusState = { + txHistory: Record; +}; + +export type BridgeStatusControllerState = { + bridgeStatusState: BridgeStatusState; +}; + +export type BridgeStatusAppState = ProviderConfigState & { + metamask: BridgeStatusControllerState; +}; + +export type MetricsBackgroundState = BridgeStatusAppState['metamask'] & + SmartTransactionsMetaMaskState['metamask'] & + NetworkState['metamask'] & + TransactionControllerState; + +// Actions +type BridgeStatusControllerAction< + FunctionName extends keyof BridgeStatusController, +> = { + type: `${typeof BRIDGE_STATUS_CONTROLLER_NAME}:${FunctionName}`; + handler: BridgeStatusController[FunctionName]; +}; + +// Maps to BridgeController function names +type BridgeStatusControllerActions = + | BridgeStatusControllerAction + | BridgeStatusControllerAction + | ControllerGetStateAction< + typeof BRIDGE_STATUS_CONTROLLER_NAME, + BridgeStatusControllerState + >; + +// Events +export type BridgeStatusControllerStateChangeEvent = ControllerStateChangeEvent< + typeof BRIDGE_STATUS_CONTROLLER_NAME, + BridgeStatusControllerState +>; + +export type BridgeStatusControllerBridgeTransactionCompleteEvent = { + type: `${typeof BRIDGE_STATUS_CONTROLLER_NAME}:bridgeTransactionComplete`; + payload: [{ bridgeHistoryItem: BridgeHistoryItem }]; +}; + +export type BridgeStatusControllerBridgeTransactionFailedEvent = { + type: `${typeof BRIDGE_STATUS_CONTROLLER_NAME}:bridgeTransactionFailed`; + payload: [{ bridgeHistoryItem: BridgeHistoryItem }]; +}; + +type BridgeStatusControllerEvents = + | BridgeStatusControllerStateChangeEvent + | BridgeStatusControllerBridgeTransactionCompleteEvent + | BridgeStatusControllerBridgeTransactionFailedEvent; + +/** + * The external actions available to the BridgeStatusController. + */ +type AllowedActions = + | NetworkControllerFindNetworkClientIdByChainIdAction + | NetworkControllerGetStateAction + | NetworkControllerGetNetworkClientByIdAction + | AccountsControllerGetSelectedAccountAction + | TransactionControllerGetStateAction; + +/** + * The external events available to the BridgeStatusController. + */ +type AllowedEvents = never; + +/** + * The messenger for the BridgeStatusController. + */ +export type BridgeStatusControllerMessenger = RestrictedMessenger< + typeof BRIDGE_STATUS_CONTROLLER_NAME, + BridgeStatusControllerActions | AllowedActions, + BridgeStatusControllerEvents | AllowedEvents, + AllowedActions['type'], + AllowedEvents['type'] +>; diff --git a/packages/bridge-status-controller/utils.ts b/packages/bridge-status-controller/utils.ts new file mode 100644 index 0000000000..5447b483e3 --- /dev/null +++ b/packages/bridge-status-controller/utils.ts @@ -0,0 +1,82 @@ +import { + BRIDGE_API_BASE_URL, + BRIDGE_CLIENT_ID, +} from '../../../../shared/constants/bridge'; +import fetchWithCache from '../../../../shared/lib/fetch-with-cache'; +import { + StatusResponse, + StatusRequestWithSrcTxHash, + StatusRequestDto, +} from '../../../../shared/types/bridge-status'; +import type { Quote } from '../../../../shared/types/bridge'; +import { validateResponse, validators } from './validators'; + +const CLIENT_ID_HEADER = { 'X-Client-Id': BRIDGE_CLIENT_ID }; + +export const BRIDGE_STATUS_BASE_URL = `${BRIDGE_API_BASE_URL}/getTxStatus`; + +export const getStatusRequestDto = ( + statusRequest: StatusRequestWithSrcTxHash, +): StatusRequestDto => { + const { quote, ...statusRequestNoQuote } = statusRequest; + + const statusRequestNoQuoteFormatted = Object.fromEntries( + Object.entries(statusRequestNoQuote).map(([key, value]) => [ + key, + value.toString(), + ]), + ) as unknown as Omit; + + const requestId: { requestId: string } | Record = + quote?.requestId ? { requestId: quote.requestId } : {}; + + return { + ...statusRequestNoQuoteFormatted, + ...requestId, + }; +}; + +export const fetchBridgeTxStatus = async ( + statusRequest: StatusRequestWithSrcTxHash, +) => { + const statusRequestDto = getStatusRequestDto(statusRequest); + const params = new URLSearchParams(statusRequestDto); + + // Fetch + const url = `${BRIDGE_STATUS_BASE_URL}?${params.toString()}`; + + const rawTxStatus = await fetchWithCache({ + url, + fetchOptions: { method: 'GET', headers: CLIENT_ID_HEADER }, + cacheOptions: { cacheRefreshTime: 0 }, + functionName: 'fetchBridgeTxStatus', + }); + + // Validate + const isValid = validateResponse( + validators, + rawTxStatus, + BRIDGE_STATUS_BASE_URL, + ); + if (!isValid) { + throw new Error('Invalid response from bridge'); + } + + // Return + return rawTxStatus; +}; + +export const getStatusRequestWithSrcTxHash = ( + quote: Quote, + srcTxHash: string, +): StatusRequestWithSrcTxHash => { + return { + bridgeId: quote.bridgeId, + srcTxHash, + bridge: quote.bridges[0], + srcChainId: quote.srcChainId, + destChainId: quote.destChainId, + quote, + refuel: Boolean(quote.refuel), + }; +}; diff --git a/packages/bridge-status-controller/validators.test.ts b/packages/bridge-status-controller/validators.test.ts new file mode 100644 index 0000000000..c294121dc6 --- /dev/null +++ b/packages/bridge-status-controller/validators.test.ts @@ -0,0 +1,295 @@ +import { StatusResponse } from '../../../../shared/types/bridge-status'; +import { validateResponse, validators } from './validators'; + +const BridgeTxStatusResponses = { + STATUS_PENDING_VALID: { + status: 'PENDING', + bridge: 'across', + srcChain: { + chainId: 42161, + txHash: + '0x76a65e4cea35d8732f0e3250faed00ba764ad5a0e7c51cb1bafbc9d76ac0b325', + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 42161, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2550.12', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: '10', + token: {}, + }, + }, + STATUS_PENDING_VALID_MISSING_FIELDS: { + status: 'PENDING', + srcChain: { + chainId: 42161, + txHash: + '0x5cbda572c686a5a57fe62735325e408f9164f77a4787df29ce13edef765adaa9', + }, + }, + STATUS_PENDING_VALID_MISSING_FIELDS_2: { + status: 'PENDING', + bridge: 'hop', + srcChain: { + chainId: 42161, + txHash: + '0x5cbda572c686a5a57fe62735325e408f9164f77a4787df29ce13edef765adaa9', + amount: '991250000000000', + token: { + chainId: 42161, + address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + symbol: 'ETH', + name: 'Ethereum', + decimals: 18, + icon: 'https://media.socket.tech/tokens/all/ETH', + logoURI: 'https://media.socket.tech/tokens/all/ETH', + chainAgnosticId: null, + }, + }, + }, + STATUS_PENDING_INVALID_MISSING_FIELDS: { + status: 'PENDING', + bridge: 'across', + srcChain: { + chainId: 42161, + txHash: + '0x76a65e4cea35d8732f0e3250faed00ba764ad5a0e7c51cb1bafbc9d76ac0b325', + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 42161, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2550.12', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + token: {}, + }, + }, + STATUS_COMPLETE_VALID: { + status: 'COMPLETE', + isExpectedToken: true, + bridge: 'across', + srcChain: { + chainId: 10, + txHash: + '0x9fdc426692aba1f81e145834602ed59ed331054e5b91a09a673cb12d4b4f6a33', + amount: '4956250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 10, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2649.21', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: '42161', + txHash: + '0x3a494e672717f9b1f2b64a48a19985842d82d0747400fccebebc7a4e99c8eaab', + amount: '4926701727965948', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 42161, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2648.72', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }, + STATUS_COMPLETE_VALID_MISSING_FIELDS: { + status: 'COMPLETE', + bridge: 'across', + srcChain: { + chainId: 10, + txHash: + '0x9fdc426692aba1f81e145834602ed59ed331054e5b91a09a673cb12d4b4f6a33', + amount: '4956250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 10, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2649.21', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: '42161', + txHash: + '0x3a494e672717f9b1f2b64a48a19985842d82d0747400fccebebc7a4e99c8eaab', + amount: '4926701727965948', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 42161, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2648.72', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }, + STATUS_COMPLETE_VALID_MISSING_FIELDS_2: { + status: 'COMPLETE', + isExpectedToken: false, + bridge: 'across', + srcChain: { + chainId: 10, + txHash: + '0x4c57876fad21fb5149af5a58a4aba2ca9d6b212014505dd733b75667ca4f0f2b', + amount: '991250000000000', + token: { + chainId: 10, + address: '0x4200000000000000000000000000000000000006', + symbol: 'WETH', + name: 'Wrapped Ether', + decimals: 18, + icon: 'https://media.socket.tech/tokens/all/WETH', + // logoURI: 'https://media.socket.tech/tokens/all/WETH', + // chainAgnosticId: 'ETH', + }, + }, + destChain: { + chainId: 8453, + txHash: + '0x60c4cad7c3eb14c7b3ace40cd4015b90927dadacbdc8673f404bea6a5603844b', + amount: '988339336750062', + token: { + chainId: 8453, + address: '0x4200000000000000000000000000000000000006', + symbol: 'WETH', + name: 'Wrapped Ether', + decimals: 18, + icon: null, + // logoURI: null, + // chainAgnosticId: null, + }, + }, + }, + STATUS_COMPLETE_INVALID_MISSING_FIELDS: { + status: 'COMPLETE', + isExpectedToken: true, + bridge: 'across', + }, + STATUS_FAILED_VALID: { + status: 'FAILED', + bridge: 'across', + srcChain: { + chainId: 42161, + txHash: + '0x4c57876fad21fb5149af5a58a4aba2ca9d6b212014505dd733b75667ca4f0f2b', + token: {}, + }, + }, +}; + +describe('validators', () => { + describe('bridgeStatusValidator', () => { + // @ts-expect-error - it.each is a function + it.each([ + { + input: BridgeTxStatusResponses.STATUS_PENDING_VALID, + expected: true, + description: 'valid pending bridge status', + }, + { + input: BridgeTxStatusResponses.STATUS_PENDING_VALID_MISSING_FIELDS, + expected: true, + description: 'valid pending bridge status missing fields', + }, + { + input: BridgeTxStatusResponses.STATUS_PENDING_VALID_MISSING_FIELDS_2, + expected: true, + description: 'valid pending bridge status missing fields 2', + }, + { + input: BridgeTxStatusResponses.STATUS_PENDING_INVALID_MISSING_FIELDS, + expected: false, + description: 'pending bridge status with missing fields', + }, + { + input: BridgeTxStatusResponses.STATUS_COMPLETE_VALID, + expected: true, + description: 'valid complete bridge status', + }, + { + input: BridgeTxStatusResponses.STATUS_COMPLETE_INVALID_MISSING_FIELDS, + expected: false, + description: 'complete bridge status with missing fields', + }, + { + input: BridgeTxStatusResponses.STATUS_COMPLETE_VALID_MISSING_FIELDS_2, + expected: true, + description: 'complete bridge status with missing fields 2', + }, + { + input: BridgeTxStatusResponses.STATUS_COMPLETE_VALID_MISSING_FIELDS, + expected: true, + description: 'complete bridge status with missing fields', + }, + { + input: BridgeTxStatusResponses.STATUS_FAILED_VALID, + expected: true, + description: 'valid failed bridge status', + }, + { + input: undefined, + expected: false, + description: 'undefined', + }, + { + input: null, + expected: false, + description: 'null', + }, + { + input: {}, + expected: false, + description: 'empty object', + }, + ])( + 'should return $expected for $description', + ({ input, expected }: { input: unknown; expected: boolean }) => { + const res = validateResponse( + validators, + input, + 'dummyurl.com', + ); + expect(res).toBe(expected); + }, + ); + }); +}); diff --git a/packages/bridge-status-controller/validators.ts b/packages/bridge-status-controller/validators.ts new file mode 100644 index 0000000000..f294828105 --- /dev/null +++ b/packages/bridge-status-controller/validators.ts @@ -0,0 +1,182 @@ +import { validHex, validateData } from '../../../../shared/lib/swaps-utils'; +import { isValidHexAddress } from '../../../../shared/modules/hexstring-utils'; +import { + BridgeId, + DestChainStatus, + SrcChainStatus, + Asset, + StatusTypes, +} from '../../../../shared/types/bridge-status'; +import { BRIDGE_STATUS_BASE_URL } from './utils'; + +type Validator = { + property: keyof ExpectedResponse | string; + type: string; + validator: (value: DataToValidate) => boolean; +}; + +export const validateResponse = ( + validators: Validator[], + data: unknown, + urlUsed: string, +): data is ExpectedResponse => { + if (data === null || data === undefined) { + return false; + } + return validateData(validators, data, urlUsed); +}; + +const assetValidators = [ + { + property: 'chainId', + type: 'number', + validator: (v: unknown): v is number => typeof v === 'number', + }, + { + property: 'address', + type: 'string', + validator: (v: unknown): v is string => isValidHexAddress(v as string), + }, + { + property: 'symbol', + type: 'string', + validator: (v: unknown): v is string => typeof v === 'string', + }, + { + property: 'name', + type: 'string', + validator: (v: unknown): v is string => typeof v === 'string', + }, + { + property: 'decimals', + type: 'number', + validator: (v: unknown): v is number => typeof v === 'number', + }, + { + property: 'icon', + // typeof null === 'object' + type: 'string|undefined|object', + validator: (v: unknown): v is string | undefined | object => + v === undefined || v === null || typeof v === 'string', + }, +]; + +const assetValidator = (v: unknown): v is Asset => + validateResponse(assetValidators, v, BRIDGE_STATUS_BASE_URL); + +const srcChainStatusValidators = [ + { + property: 'chainId', + // For some reason, API returns destChain.chainId as a string, it's a number everywhere else + type: 'number|string', + validator: (v: unknown): v is number | string => + typeof v === 'number' || typeof v === 'string', + }, + { + property: 'txHash', + type: 'string', + validator: validHex, + }, + { + property: 'amount', + type: 'string|undefined', + validator: (v: unknown): v is string | undefined => + v === undefined || typeof v === 'string', + }, + { + property: 'token', + type: 'object|undefined', + validator: (v: unknown): v is object | undefined => + v === undefined || + (v && typeof v === 'object' && Object.keys(v).length === 0) || + assetValidator(v), + }, +]; + +const srcChainStatusValidator = (v: unknown): v is SrcChainStatus => + validateResponse( + srcChainStatusValidators, + v, + BRIDGE_STATUS_BASE_URL, + ); + +const destChainStatusValidators = [ + { + property: 'chainId', + // For some reason, API returns destChain.chainId as a string, it's a number everywhere else + type: 'number|string', + validator: (v: unknown): v is number | string => + typeof v === 'number' || typeof v === 'string', + }, + { + property: 'amount', + type: 'string|undefined', + validator: (v: unknown): v is string | undefined => + v === undefined || typeof v === 'string', + }, + { + property: 'txHash', + type: 'string|undefined', + validator: (v: unknown): v is string | undefined => + v === undefined || typeof v === 'string', + }, + { + property: 'token', + type: 'object|undefined', + validator: (v: unknown): v is Asset | undefined => + v === undefined || + (v && typeof v === 'object' && Object.keys(v).length === 0) || + assetValidator(v), + }, +]; + +const destChainStatusValidator = (v: unknown): v is DestChainStatus => + validateResponse( + destChainStatusValidators, + v, + BRIDGE_STATUS_BASE_URL, + ); + +export const validators = [ + { + property: 'status', + type: 'string', + validator: (v: unknown): v is StatusTypes => + Object.values(StatusTypes).includes(v as StatusTypes), + }, + { + property: 'srcChain', + type: 'object', + validator: srcChainStatusValidator, + }, + { + property: 'destChain', + type: 'object|undefined', + validator: (v: unknown): v is object | unknown => + v === undefined || destChainStatusValidator(v), + }, + { + property: 'bridge', + type: 'string|undefined', + validator: (v: unknown): v is BridgeId | undefined => + v === undefined || Object.values(BridgeId).includes(v as BridgeId), + }, + { + property: 'isExpectedToken', + type: 'boolean|undefined', + validator: (v: unknown): v is boolean | undefined => + v === undefined || typeof v === 'boolean', + }, + { + property: 'isUnrecognizedRouterAddress', + type: 'boolean|undefined', + validator: (v: unknown): v is boolean | undefined => + v === undefined || typeof v === 'boolean', + }, + // TODO: add refuel validator + // { + // property: 'refuel', + // type: 'object', + // validator: (v: unknown) => Object.values(RefuelStatusResponse).includes(v), + // }, +]; From c69bcdc506780f95c18667cba3174588045d1075 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 12 Feb 2025 16:17:39 -0500 Subject: [PATCH 08/49] chore: update packages --- .../bridge-status-controller/package.json | 17 ++++++++------- .../tsconfig.build.json | 4 +--- .../bridge-status-controller/tsconfig.json | 1 + yarn.lock | 21 ++++++++++--------- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 1b4ae17aed..3cf965af2b 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -47,21 +47,21 @@ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch" }, "dependencies": { - "@metamask/accounts-controller": "^23.0.0", - "@metamask/base-controller": "^7.1.1", + "@metamask/base-controller": "^8.0.0", "@metamask/controller-utils": "^11.5.0", "@metamask/metamask-eth-abis": "^3.1.1", - "@metamask/network-controller": "^22.2.0", - "@metamask/polling-controller": "^12.0.2", - "@metamask/transaction-controller": "^45.0.0", + "@metamask/polling-controller": "^12.0.3", "@metamask/utils": "^11.1.0", - "ethereumjs-util": "^7.0.10", "ethers": "^6.12.0" }, "devDependencies": { + "@metamask/accounts-controller": "^23.1.0", "@metamask/auto-changelog": "^3.4.4", + "@metamask/bridge-controller": "^0.0.0", "@metamask/eth-json-rpc-provider": "^4.1.8", "@metamask/json-rpc-engine": "^10.0.3", + "@metamask/network-controller": "^22.2.1", + "@metamask/transaction-controller": "^45.1.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", @@ -74,8 +74,9 @@ "typescript": "~5.2.2" }, "peerDependencies": { - "@metamask/network-controller": "^22.0.0", - "@metamask/transaction-controller": "^45.0.0" + "@metamask/accounts-controller": "^23.1.0", + "@metamask/network-controller": "^22.2.1", + "@metamask/transaction-controller": "^45.1.0" }, "engines": { "node": "^18.18 || >=20" diff --git a/packages/bridge-status-controller/tsconfig.build.json b/packages/bridge-status-controller/tsconfig.build.json index 5d38b99686..2b7b7f52b5 100644 --- a/packages/bridge-status-controller/tsconfig.build.json +++ b/packages/bridge-status-controller/tsconfig.build.json @@ -7,12 +7,10 @@ }, "references": [ { "path": "../accounts-controller/tsconfig.build.json" }, - { "path": "../approval-controller/tsconfig.build.json" }, { "path": "../base-controller/tsconfig.build.json" }, + { "path": "../bridge-controller/tsconfig.build.json" }, { "path": "../controller-utils/tsconfig.build.json" }, - { "path": "../keyring-controller/tsconfig.build.json" }, { "path": "../network-controller/tsconfig.build.json" }, - { "path": "../preferences-controller/tsconfig.build.json" }, { "path": "../polling-controller/tsconfig.build.json" } ], "include": ["../../types", "./src"] diff --git a/packages/bridge-status-controller/tsconfig.json b/packages/bridge-status-controller/tsconfig.json index f989b1ba27..49cc85ffa3 100644 --- a/packages/bridge-status-controller/tsconfig.json +++ b/packages/bridge-status-controller/tsconfig.json @@ -7,6 +7,7 @@ "references": [ { "path": "../accounts-controller" }, { "path": "../base-controller" }, + { "path": "../bridge-controller" }, { "path": "../controller-utils" }, { "path": "../polling-controller" }, { "path": "../network-controller" }, diff --git a/yarn.lock b/yarn.lock index bb164366d8..c9cf67b076 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2582,7 +2582,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/bridge-controller@workspace:packages/bridge-controller": +"@metamask/bridge-controller@npm:^0.0.0, @metamask/bridge-controller@workspace:packages/bridge-controller": version: 0.0.0-use.local resolution: "@metamask/bridge-controller@workspace:packages/bridge-controller" dependencies: @@ -2619,20 +2619,20 @@ __metadata: version: 0.0.0-use.local resolution: "@metamask/bridge-status-controller@workspace:packages/bridge-status-controller" dependencies: - "@metamask/accounts-controller": "npm:^23.0.0" + "@metamask/accounts-controller": "npm:^23.1.0" "@metamask/auto-changelog": "npm:^3.4.4" - "@metamask/base-controller": "npm:^7.1.1" + "@metamask/base-controller": "npm:^8.0.0" + "@metamask/bridge-controller": "npm:^0.0.0" "@metamask/controller-utils": "npm:^11.5.0" "@metamask/eth-json-rpc-provider": "npm:^4.1.8" "@metamask/json-rpc-engine": "npm:^10.0.3" "@metamask/metamask-eth-abis": "npm:^3.1.1" - "@metamask/network-controller": "npm:^22.2.0" - "@metamask/polling-controller": "npm:^12.0.2" - "@metamask/transaction-controller": "npm:^45.0.0" + "@metamask/network-controller": "npm:^22.2.1" + "@metamask/polling-controller": "npm:^12.0.3" + "@metamask/transaction-controller": "npm:^45.1.0" "@metamask/utils": "npm:^11.1.0" "@types/jest": "npm:^27.4.1" deepmerge: "npm:^4.2.2" - ethereumjs-util: "npm:^7.0.10" ethers: "npm:^6.12.0" jest: "npm:^27.5.1" jest-environment-jsdom: "npm:^27.5.1" @@ -2643,8 +2643,9 @@ __metadata: typedoc-plugin-missing-exports: "npm:^2.0.0" typescript: "npm:~5.2.2" peerDependencies: - "@metamask/network-controller": ^22.0.0 - "@metamask/transaction-controller": ^45.0.0 + "@metamask/accounts-controller": ^23.1.0 + "@metamask/network-controller": ^22.2.1 + "@metamask/transaction-controller": ^45.1.0 languageName: unknown linkType: soft @@ -13987,4 +13988,4 @@ __metadata: "@types/yoga-layout": "npm:1.9.2" checksum: 10/fe36fadae9b30710083f76c73e87479c2eb291ff7c560c35a9e2b8eb78f43882ace63cc80cdaecae98ee2e4168e1bf84dc65b2f5ae1bfa31df37603c46683bd6 languageName: node - linkType: hard \ No newline at end of file + linkType: hard From d2ce2e69ee733d492764f5527821f95590adb5ca Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:03:55 -0500 Subject: [PATCH 09/49] chore: get all imports fixed --- README.md | 1 + .../bridge-status-controller.test.ts | 117 +++++++++++------- .../{ => src}/bridge-status-controller.ts | 78 +++++++----- .../{ => src}/constants.ts | 2 +- .../{ => src}/mocks.ts | 7 +- .../{ => src}/types.ts | 37 ++---- .../{ => src}/utils.ts | 29 +++-- .../{ => src}/validators.test.ts | 3 +- .../{ => src}/validators.ts | 55 ++++++-- teams.json | 1 + tsconfig.build.json | 2 + tsconfig.json | 2 + 12 files changed, 206 insertions(+), 128 deletions(-) rename packages/bridge-status-controller/{ => src}/bridge-status-controller.test.ts (81%) rename packages/bridge-status-controller/{ => src}/bridge-status-controller.ts (88%) rename packages/bridge-status-controller/{ => src}/constants.ts (80%) rename packages/bridge-status-controller/{ => src}/mocks.ts (99%) rename packages/bridge-status-controller/{ => src}/types.ts (92%) rename packages/bridge-status-controller/{ => src}/utils.ts (71%) rename packages/bridge-status-controller/{ => src}/validators.test.ts (98%) rename packages/bridge-status-controller/{ => src}/validators.ts (76%) diff --git a/README.md b/README.md index d35222d254..06b90d078f 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Each package in this repository has its own README where you can find installati - [`@metamask/assets-controllers`](packages/assets-controllers) - [`@metamask/base-controller`](packages/base-controller) - [`@metamask/bridge-controller`](packages/bridge-controller) +- [`@metamask/bridge-status-controller`](packages/bridge-status-controller) - [`@metamask/build-utils`](packages/build-utils) - [`@metamask/composable-controller`](packages/composable-controller) - [`@metamask/controller-utils`](packages/controller-utils) diff --git a/packages/bridge-status-controller/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts similarity index 81% rename from packages/bridge-status-controller/bridge-status-controller.test.ts rename to packages/bridge-status-controller/src/bridge-status-controller.test.ts index aab6985511..6e08413ae8 100644 --- a/packages/bridge-status-controller/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -1,14 +1,16 @@ -import { flushPromises } from '../../../../test/lib/timer-helpers'; -import { Numeric } from '../../../../shared/modules/Numeric'; +import { BridgeClientId } from '@metamask/bridge-controller'; +import { numberToHex } from '@metamask/utils'; + import BridgeStatusController from './bridge-status-controller'; -import { BridgeStatusControllerMessenger } from './types'; import { DEFAULT_BRIDGE_STATUS_STATE } from './constants'; -import * as bridgeStatusUtils from './utils'; import { MockStatusResponse, MockTxHistory, getMockStartPollingForBridgeTxStatusArgs, } from './mocks'; +import type { BridgeStatusControllerMessenger } from './types'; +import * as bridgeStatusUtils from './utils'; +import { flushPromises } from '../../../tests/helpers'; const EMPTY_INIT_STATE = { bridgeStatusState: { ...DEFAULT_BRIDGE_STATUS_STATE }, @@ -29,7 +31,7 @@ const getMessengerMock = ({ } else if (method === 'NetworkController:getNetworkClientById') { return { configuration: { - chainId: new Numeric(srcChainId, 10).toPrefixedHexString(), + chainId: numberToHex(srcChainId), }, }; } @@ -38,13 +40,15 @@ const getMessengerMock = ({ publish: jest.fn(), registerActionHandler: jest.fn(), registerInitialEventPayload: jest.fn(), - } as unknown as jest.Mocked); + }) as unknown as jest.Mocked; const executePollingWithPendingStatus = async () => { // Setup jest.useFakeTimers(); const bridgeStatusController = new BridgeStatusController({ messenger: getMessengerMock(), + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), }); const startPollingSpy = jest.spyOn(bridgeStatusController, 'startPolling'); const fetchBridgeTxStatusSpy = jest.spyOn( @@ -53,7 +57,7 @@ const executePollingWithPendingStatus = async () => { ); // Execution - await bridgeStatusController.startPollingForBridgeTxStatus( + bridgeStatusController.startPollingForBridgeTxStatus( getMockStartPollingForBridgeTxStatusArgs(), ); fetchBridgeTxStatusSpy.mockImplementationOnce(async () => { @@ -74,13 +78,17 @@ describe('BridgeStatusController', () => { it('should setup correctly', () => { const bridgeStatusController = new BridgeStatusController({ messenger: getMessengerMock(), + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), }); - expect(bridgeStatusController.state).toEqual(EMPTY_INIT_STATE); + expect(bridgeStatusController.state).toStrictEqual(EMPTY_INIT_STATE); }); it('rehydrates the tx history state', async () => { // Setup const bridgeStatusController = new BridgeStatusController({ messenger: getMessengerMock(), + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), state: { bridgeStatusState: { txHistory: MockTxHistory.getPending(), @@ -89,7 +97,7 @@ describe('BridgeStatusController', () => { }); // Execution - await bridgeStatusController.startPollingForBridgeTxStatus( + bridgeStatusController.startPollingForBridgeTxStatus( getMockStartPollingForBridgeTxStatusArgs(), ); @@ -115,6 +123,8 @@ describe('BridgeStatusController', () => { txHistory: MockTxHistory.getPending(), }, }, + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), }); jest.advanceTimersByTime(10000); await flushPromises(); @@ -128,10 +138,12 @@ describe('BridgeStatusController', () => { // Setup const bridgeStatusController = new BridgeStatusController({ messenger: getMessengerMock(), + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), }); // Execution - await bridgeStatusController.startPollingForBridgeTxStatus( + bridgeStatusController.startPollingForBridgeTxStatus( getMockStartPollingForBridgeTxStatusArgs(), ); @@ -150,21 +162,21 @@ describe('BridgeStatusController', () => { // Assertions expect(startPollingSpy).toHaveBeenCalledTimes(1); expect(fetchBridgeTxStatusSpy).toHaveBeenCalled(); - expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( - MockTxHistory.getPending(), - ); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toStrictEqual(MockTxHistory.getPending()); }); it('stops polling when the status response is complete', async () => { // Setup jest.useFakeTimers(); - jest - .spyOn(Date, 'now') - .mockImplementation( - () => - MockTxHistory.getComplete().bridgeTxMetaId1.completionTime ?? 10, - ); + jest.spyOn(Date, 'now').mockImplementation(() => { + // eslint-disable-next-line jest/no-conditional-in-test + return MockTxHistory.getComplete().bridgeTxMetaId1.completionTime ?? 10; + }); const bridgeStatusController = new BridgeStatusController({ messenger: getMessengerMock(), + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest.spyOn( bridgeStatusUtils, @@ -176,7 +188,7 @@ describe('BridgeStatusController', () => { ); // Execution - await bridgeStatusController.startPollingForBridgeTxStatus( + bridgeStatusController.startPollingForBridgeTxStatus( getMockStartPollingForBridgeTxStatusArgs(), ); fetchBridgeTxStatusSpy.mockImplementationOnce(async () => { @@ -187,9 +199,9 @@ describe('BridgeStatusController', () => { // Assertions expect(stopPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1); - expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( - MockTxHistory.getComplete(), - ); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toStrictEqual(MockTxHistory.getComplete()); jest.restoreAllMocks(); }); @@ -199,13 +211,13 @@ describe('BridgeStatusController', () => { const { bridgeStatusController } = await executePollingWithPendingStatus(); - expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( - MockTxHistory.getPending(), - ); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toStrictEqual(MockTxHistory.getPending()); bridgeStatusController.resetState(); - expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( - EMPTY_INIT_STATE.bridgeStatusState.txHistory, - ); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toStrictEqual(EMPTY_INIT_STATE.bridgeStatusState.txHistory); }); }); describe('wipeBridgeStatus', () => { @@ -216,8 +228,10 @@ describe('BridgeStatusController', () => { let getSelectedAccountCalledTimes = 0; const messengerMock = { call: jest.fn((method: string) => { + // eslint-disable-next-line jest/no-conditional-in-test if (method === 'AccountsController:getSelectedAccount') { let account; + // eslint-disable-next-line jest/no-conditional-in-test if (getSelectedAccountCalledTimes === 0) { account = '0xaccount1'; } else { @@ -225,16 +239,19 @@ describe('BridgeStatusController', () => { } getSelectedAccountCalledTimes += 1; return { address: account }; + // eslint-disable-next-line jest/no-conditional-in-test } else if ( method === 'NetworkController:findNetworkClientIdByChainId' ) { return 'networkClientId'; + // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getState') { return { selectedNetworkClientId: 'networkClientId' }; + // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getNetworkClientById') { return { configuration: { - chainId: new Numeric(42161, 10).toPrefixedHexString(), + chainId: numberToHex(42161), }, }; } @@ -246,6 +263,8 @@ describe('BridgeStatusController', () => { } as unknown as jest.Mocked; const bridgeStatusController = new BridgeStatusController({ messenger: messengerMock, + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') @@ -296,25 +315,29 @@ describe('BridgeStatusController', () => { bridgeStatusController.state.bridgeStatusState.txHistory, ); expect(txHistoryItems).toHaveLength(1); - expect(txHistoryItems[0].account).toEqual('0xaccount2'); + expect(txHistoryItems[0].account).toBe('0xaccount2'); }); it('wipes the bridge status for all networks if ignoreNetwork is true', () => { // Setup jest.useFakeTimers(); const messengerMock = { call: jest.fn((method: string) => { + // eslint-disable-next-line jest/no-conditional-in-test if (method === 'AccountsController:getSelectedAccount') { return { address: '0xaccount1' }; + // eslint-disable-next-line jest/no-conditional-in-test } else if ( method === 'NetworkController:findNetworkClientIdByChainId' ) { return 'networkClientId'; + // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getState') { return { selectedNetworkClientId: 'networkClientId' }; + // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getNetworkClientById') { return { configuration: { - chainId: new Numeric(42161, 10).toPrefixedHexString(), + chainId: numberToHex(42161), }, }; } @@ -326,6 +349,8 @@ describe('BridgeStatusController', () => { } as unknown as jest.Mocked; const bridgeStatusController = new BridgeStatusController({ messenger: messengerMock, + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') @@ -368,20 +393,20 @@ describe('BridgeStatusController', () => { expect( bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 .quote.srcChainId, - ).toEqual(42161); + ).toBe(42161); expect( bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 .quote.destChainId, - ).toEqual(1); + ).toBe(1); expect( bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 .quote.srcChainId, - ).toEqual(10); + ).toBe(10); expect( bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 .quote.destChainId, - ).toEqual(123); + ).toBe(123); bridgeStatusController.wipeBridgeStatus({ address: '0xaccount1', @@ -399,19 +424,23 @@ describe('BridgeStatusController', () => { jest.useFakeTimers(); const messengerMock = { call: jest.fn((method: string) => { + // eslint-disable-next-line jest/no-conditional-in-test if (method === 'AccountsController:getSelectedAccount') { return { address: '0xaccount1' }; + // eslint-disable-next-line jest/no-conditional-in-test } else if ( method === 'NetworkController:findNetworkClientIdByChainId' ) { return 'networkClientId'; + // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getState') { return { selectedNetworkClientId: 'networkClientId' }; + // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getNetworkClientById') { return { configuration: { // This is what controls the selectedNetwork and what gets wiped in this test - chainId: new Numeric(42161, 10).toPrefixedHexString(), + chainId: numberToHex(42161), }, }; } @@ -423,6 +452,8 @@ describe('BridgeStatusController', () => { } as unknown as jest.Mocked; const bridgeStatusController = new BridgeStatusController({ messenger: messengerMock, + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') @@ -465,20 +496,20 @@ describe('BridgeStatusController', () => { expect( bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 .quote.srcChainId, - ).toEqual(42161); + ).toBe(42161); expect( bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 .quote.destChainId, - ).toEqual(1); + ).toBe(1); expect( bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 .quote.srcChainId, - ).toEqual(10); + ).toBe(10); expect( bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 .quote.destChainId, - ).toEqual(123); + ).toBe(123); bridgeStatusController.wipeBridgeStatus({ address: '0xaccount1', @@ -490,8 +521,8 @@ describe('BridgeStatusController', () => { bridgeStatusController.state.bridgeStatusState.txHistory, ); expect(txHistoryItems).toHaveLength(1); - expect(txHistoryItems[0].quote.srcChainId).toEqual(10); - expect(txHistoryItems[0].quote.destChainId).toEqual(123); + expect(txHistoryItems[0].quote.srcChainId).toBe(10); + expect(txHistoryItems[0].quote.destChainId).toBe(123); }); }); }); diff --git a/packages/bridge-status-controller/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts similarity index 88% rename from packages/bridge-status-controller/bridge-status-controller.ts rename to packages/bridge-status-controller/src/bridge-status-controller.ts index 3c278b1889..21b4817b63 100644 --- a/packages/bridge-status-controller/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -1,20 +1,20 @@ -import { StateMetadata } from '@metamask/base-controller'; +import type { StateMetadata } from '@metamask/base-controller'; +import type { BridgeClientId } from '@metamask/bridge-controller'; import { StaticIntervalPollingController } from '@metamask/polling-controller'; -import { Hex } from '@metamask/utils'; -// eslint-disable-next-line import/no-restricted-paths -import { - StatusTypes, - BridgeStatusControllerState, - StartPollingForBridgeTxStatusArgsSerialized, - BridgeStatusState, -} from '../../../../shared/types/bridge-status'; -import { decimalToPrefixedHex } from '../../../../shared/modules/conversion.utils'; +import { numberToHex, type Hex } from '@metamask/utils'; + import { BRIDGE_STATUS_CONTROLLER_NAME, DEFAULT_BRIDGE_STATUS_STATE, REFRESH_INTERVAL_MS, } from './constants'; -import { BridgeStatusControllerMessenger } from './types'; +import { StatusTypes, type BridgeStatusControllerMessenger } from './types'; +import type { + BridgeStatusControllerState, + StartPollingForBridgeTxStatusArgsSerialized, + BridgeStatusState, + FetchFunction, +} from './types'; import { fetchBridgeTxStatus, getStatusRequestWithSrcTxHash } from './utils'; const metadata: StateMetadata = { @@ -40,12 +40,20 @@ export default class BridgeStatusController extends StaticIntervalPollingControl > { #pollingTokensByTxMetaId: Record = {}; + readonly #clientId: BridgeClientId; + + readonly #fetchFn: FetchFunction; + constructor({ messenger, state, + clientId, + fetchFn, }: { messenger: BridgeStatusControllerMessenger; state?: { bridgeStatusState?: Partial }; + clientId: BridgeClientId; + fetchFn: FetchFunction; }) { super({ name: BRIDGE_STATUS_CONTROLLER_NAME, @@ -61,6 +69,9 @@ export default class BridgeStatusController extends StaticIntervalPollingControl }, }); + this.#clientId = clientId; + this.#fetchFn = fetchFn; + // Register action handlers this.messagingSystem.registerActionHandler( `${BRIDGE_STATUS_CONTROLLER_NAME}:startPollingForBridgeTxStatus`, @@ -81,8 +92,8 @@ export default class BridgeStatusController extends StaticIntervalPollingControl } resetState = () => { - this.update((_state) => { - _state.bridgeStatusState = { + this.update((state) => { + state.bridgeStatusState = { ...DEFAULT_BRIDGE_STATUS_STATE, }; }); @@ -97,8 +108,8 @@ export default class BridgeStatusController extends StaticIntervalPollingControl }) => { // Wipe all networks for this address if (ignoreNetwork) { - this.update((_state) => { - _state.bridgeStatusState = { + this.update((state) => { + state.bridgeStatusState = { ...DEFAULT_BRIDGE_STATUS_STATE, }; }); @@ -116,7 +127,7 @@ export default class BridgeStatusController extends StaticIntervalPollingControl } }; - #restartPollingForIncompleteHistoryItems = () => { + readonly #restartPollingForIncompleteHistoryItems = () => { // Check for historyItems that do not have a status of complete and restart polling const { bridgeStatusState } = this.state; const historyItems = Object.values(bridgeStatusState.txHistory); @@ -214,7 +225,7 @@ export default class BridgeStatusController extends StaticIntervalPollingControl return this.messagingSystem.call('AccountsController:getSelectedAccount'); } - #fetchBridgeTxStatus = async ({ + readonly #fetchBridgeTxStatus = async ({ bridgeTxMetaId, }: FetchBridgeTxStatusArgs) => { const { bridgeStatusState } = this.state; @@ -235,7 +246,11 @@ export default class BridgeStatusController extends StaticIntervalPollingControl historyItem.quote, srcTxHash, ); - const status = await fetchBridgeTxStatus(statusRequest); + const status = await fetchBridgeTxStatus( + statusRequest, + this.#clientId, + this.#fetchFn, + ); const newBridgeHistoryItem = { ...historyItem, status, @@ -250,8 +265,8 @@ export default class BridgeStatusController extends StaticIntervalPollingControl // TODO In theory we can skip checking status if it's not the current account/network // we need to keep track of the account that this is associated with as well so that we don't show it in Activity list for other accounts // First stab at this will not stop polling when you are on a different account - this.update((_state) => { - _state.bridgeStatusState = { + this.update((state) => { + state.bridgeStatusState = { ...bridgeStatusState, txHistory: { ...bridgeStatusState.txHistory, @@ -287,7 +302,7 @@ export default class BridgeStatusController extends StaticIntervalPollingControl } }; - #getSrcTxHash = (bridgeTxMetaId: string): string | undefined => { + readonly #getSrcTxHash = (bridgeTxMetaId: string): string | undefined => { const { bridgeStatusState } = this.state; // Prefer the srcTxHash from bridgeStatusState so we don't have to l ook up in TransactionController // But it is possible to have bridgeHistoryItem in state without the srcTxHash yet when it is an STX @@ -308,14 +323,14 @@ export default class BridgeStatusController extends StaticIntervalPollingControl return txMeta?.hash; }; - #updateSrcTxHash = (bridgeTxMetaId: string, srcTxHash: string) => { + readonly #updateSrcTxHash = (bridgeTxMetaId: string, srcTxHash: string) => { const { bridgeStatusState } = this.state; if (bridgeStatusState.txHistory[bridgeTxMetaId].status.srcChain.txHash) { return; } - this.update((_state) => { - _state.bridgeStatusState = { + this.update((state) => { + state.bridgeStatusState = { ...bridgeStatusState, txHistory: { ...bridgeStatusState.txHistory, @@ -336,16 +351,17 @@ export default class BridgeStatusController extends StaticIntervalPollingControl // Wipes the bridge status for the given address and chainId // Will match only source chainId to the selectedChainId - #wipeBridgeStatusByChainId = (address: string, selectedChainId: Hex) => { + readonly #wipeBridgeStatusByChainId = ( + address: string, + selectedChainId: Hex, + ) => { const sourceTxMetaIdsToDelete = Object.keys( this.state.bridgeStatusState.txHistory, ).filter((txMetaId) => { const bridgeHistoryItem = this.state.bridgeStatusState.txHistory[txMetaId]; - const hexSourceChainId = decimalToPrefixedHex( - bridgeHistoryItem.quote.srcChainId, - ); + const hexSourceChainId = numberToHex(bridgeHistoryItem.quote.srcChainId); return ( bridgeHistoryItem.account === address && @@ -363,13 +379,13 @@ export default class BridgeStatusController extends StaticIntervalPollingControl } }); - this.update((_state) => { - _state.bridgeStatusState.txHistory = sourceTxMetaIdsToDelete.reduce( + this.update((state) => { + state.bridgeStatusState.txHistory = sourceTxMetaIdsToDelete.reduce( (acc, sourceTxMetaId) => { delete acc[sourceTxMetaId]; return acc; }, - _state.bridgeStatusState.txHistory, + state.bridgeStatusState.txHistory, ); }); }; diff --git a/packages/bridge-status-controller/constants.ts b/packages/bridge-status-controller/src/constants.ts similarity index 80% rename from packages/bridge-status-controller/constants.ts rename to packages/bridge-status-controller/src/constants.ts index da17f195c2..bc523df71d 100644 --- a/packages/bridge-status-controller/constants.ts +++ b/packages/bridge-status-controller/src/constants.ts @@ -1,4 +1,4 @@ -import { BridgeStatusState } from '../../../../shared/types/bridge-status'; +import type { BridgeStatusState } from './types'; export const REFRESH_INTERVAL_MS = 10 * 1000; diff --git a/packages/bridge-status-controller/mocks.ts b/packages/bridge-status-controller/src/mocks.ts similarity index 99% rename from packages/bridge-status-controller/mocks.ts rename to packages/bridge-status-controller/src/mocks.ts index 80c3cf6cdf..13ec11607c 100644 --- a/packages/bridge-status-controller/mocks.ts +++ b/packages/bridge-status-controller/src/mocks.ts @@ -1,12 +1,13 @@ -import { TransactionMeta } from '@metamask/transaction-controller'; -import { +import type { TransactionMeta } from '@metamask/transaction-controller'; + +import type { BridgeId, StatusResponse, StatusTypes, ActionTypes, StartPollingForBridgeTxStatusArgsSerialized, BridgeHistoryItem, -} from '../../../../shared/types/bridge-status'; +} from './types'; export const MockStatusResponse = { getPending: ({ diff --git a/packages/bridge-status-controller/types.ts b/packages/bridge-status-controller/src/types.ts similarity index 92% rename from packages/bridge-status-controller/types.ts rename to packages/bridge-status-controller/src/types.ts index 4045fc1183..5fd61516c0 100644 --- a/packages/bridge-status-controller/types.ts +++ b/packages/bridge-status-controller/src/types.ts @@ -4,34 +4,32 @@ import type { ControllerStateChangeEvent, RestrictedMessenger, } from '@metamask/base-controller'; +import type { + ChainId, + Quote, + QuoteMetadata, + QuoteResponse, +} from '@metamask/bridge-controller'; import type { NetworkControllerFindNetworkClientIdByChainIdAction, NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction, } from '@metamask/network-controller'; import type { TransactionControllerGetStateAction } from '@metamask/transaction-controller'; -import type { - TransactionControllerState, - TransactionMeta, -} from '@metamask/transaction-controller'; +import type { TransactionMeta } from '@metamask/transaction-controller'; -import type { ChainId, Quote, QuoteMetadata, QuoteResponse } from './bridge'; import type BridgeStatusController from './bridge-status-controller'; import type { BRIDGE_STATUS_CONTROLLER_NAME } from './constants'; -import type { - BridgeHistoryItem, - BridgeStatusAction, - BridgeStatusControllerState, -} from '../../../../shared/types/bridge-status'; -import type { SmartTransactionsMetaMaskState } from '../modules/selectors'; -import type { - NetworkState, - ProviderConfigState, -} from '../modules/selectors/networks'; // All fields need to be types not interfaces, same with their children fields // o/w you get a type error +export type FetchFunction = ( + input: RequestInfo | URL, + init?: RequestInit, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +) => Promise; + export enum StatusTypes { UNKNOWN = 'UNKNOWN', FAILED = 'FAILED', @@ -256,15 +254,6 @@ export type BridgeStatusControllerState = { bridgeStatusState: BridgeStatusState; }; -export type BridgeStatusAppState = ProviderConfigState & { - metamask: BridgeStatusControllerState; -}; - -export type MetricsBackgroundState = BridgeStatusAppState['metamask'] & - SmartTransactionsMetaMaskState['metamask'] & - NetworkState['metamask'] & - TransactionControllerState; - // Actions type BridgeStatusControllerAction< FunctionName extends keyof BridgeStatusController, diff --git a/packages/bridge-status-controller/utils.ts b/packages/bridge-status-controller/src/utils.ts similarity index 71% rename from packages/bridge-status-controller/utils.ts rename to packages/bridge-status-controller/src/utils.ts index 5447b483e3..0b8c0a7e9a 100644 --- a/packages/bridge-status-controller/utils.ts +++ b/packages/bridge-status-controller/src/utils.ts @@ -1,19 +1,19 @@ -import { - BRIDGE_API_BASE_URL, - BRIDGE_CLIENT_ID, -} from '../../../../shared/constants/bridge'; -import fetchWithCache from '../../../../shared/lib/fetch-with-cache'; -import { +import type { Quote } from '@metamask/bridge-controller'; +import { getBridgeApiBaseUrl } from '@metamask/bridge-controller'; + +import type { StatusResponse, StatusRequestWithSrcTxHash, StatusRequestDto, -} from '../../../../shared/types/bridge-status'; -import type { Quote } from '../../../../shared/types/bridge'; + FetchFunction, +} from './types'; import { validateResponse, validators } from './validators'; -const CLIENT_ID_HEADER = { 'X-Client-Id': BRIDGE_CLIENT_ID }; +export const getClientIdHeader = (clientId: string) => ({ + 'X-Client-Id': clientId, +}); -export const BRIDGE_STATUS_BASE_URL = `${BRIDGE_API_BASE_URL}/getTxStatus`; +export const BRIDGE_STATUS_BASE_URL = `${getBridgeApiBaseUrl()}/getTxStatus`; export const getStatusRequestDto = ( statusRequest: StatusRequestWithSrcTxHash, @@ -38,6 +38,8 @@ export const getStatusRequestDto = ( export const fetchBridgeTxStatus = async ( statusRequest: StatusRequestWithSrcTxHash, + clientId: string, + fetchFn: FetchFunction, ) => { const statusRequestDto = getStatusRequestDto(statusRequest); const params = new URLSearchParams(statusRequestDto); @@ -45,11 +47,8 @@ export const fetchBridgeTxStatus = async ( // Fetch const url = `${BRIDGE_STATUS_BASE_URL}?${params.toString()}`; - const rawTxStatus = await fetchWithCache({ - url, - fetchOptions: { method: 'GET', headers: CLIENT_ID_HEADER }, - cacheOptions: { cacheRefreshTime: 0 }, - functionName: 'fetchBridgeTxStatus', + const rawTxStatus = await fetchFn(url, { + headers: getClientIdHeader(clientId), }); // Validate diff --git a/packages/bridge-status-controller/validators.test.ts b/packages/bridge-status-controller/src/validators.test.ts similarity index 98% rename from packages/bridge-status-controller/validators.test.ts rename to packages/bridge-status-controller/src/validators.test.ts index c294121dc6..00c6cd7c93 100644 --- a/packages/bridge-status-controller/validators.test.ts +++ b/packages/bridge-status-controller/src/validators.test.ts @@ -1,4 +1,4 @@ -import { StatusResponse } from '../../../../shared/types/bridge-status'; +import type { StatusResponse } from './types'; import { validateResponse, validators } from './validators'; const BridgeTxStatusResponses = { @@ -218,7 +218,6 @@ const BridgeTxStatusResponses = { describe('validators', () => { describe('bridgeStatusValidator', () => { - // @ts-expect-error - it.each is a function it.each([ { input: BridgeTxStatusResponses.STATUS_PENDING_VALID, diff --git a/packages/bridge-status-controller/validators.ts b/packages/bridge-status-controller/src/validators.ts similarity index 76% rename from packages/bridge-status-controller/validators.ts rename to packages/bridge-status-controller/src/validators.ts index f294828105..21697baf21 100644 --- a/packages/bridge-status-controller/validators.ts +++ b/packages/bridge-status-controller/src/validators.ts @@ -1,12 +1,7 @@ -import { validHex, validateData } from '../../../../shared/lib/swaps-utils'; -import { isValidHexAddress } from '../../../../shared/modules/hexstring-utils'; -import { - BridgeId, - DestChainStatus, - SrcChainStatus, - Asset, - StatusTypes, -} from '../../../../shared/types/bridge-status'; +import { isValidHexAddress } from '@metamask/controller-utils'; + +import type { DestChainStatus, SrcChainStatus, Asset } from './types'; +import { BridgeId, StatusTypes } from './types'; import { BRIDGE_STATUS_BASE_URL } from './utils'; type Validator = { @@ -15,6 +10,48 @@ type Validator = { validator: (value: DataToValidate) => boolean; }; +export const validHex = (value: unknown) => + typeof value === 'string' && Boolean(value.match(/^0x[a-f0-9]+$/u)); +const isValidObject = (v: unknown): v is object => + typeof v === 'object' && v !== null; + +export const validateData = ( + validators: Validator[], + object: unknown, + urlUsed: string, + logError = true, +): object is ExpectedResponse => { + return validators.every(({ property, type, validator }) => { + const types = type.split('|'); + const propertyString = String(property); + + const valid = + isValidObject(object) && + types.some( + (_type) => + typeof object[propertyString as keyof typeof object] === _type, + ) && + (!validator || validator(object[propertyString as keyof typeof object])); + + if (!valid && logError) { + const value = isValidObject(object) + ? object[propertyString as keyof typeof object] + : undefined; + const typeString = isValidObject(object) + ? typeof object[propertyString as keyof typeof object] + : 'undefined'; + + console.error( + `response to GET ${urlUsed} invalid for property ${String(property)}; value was:`, + value, + '| type was: ', + typeString, + ); + } + return valid; + }); +}; + export const validateResponse = ( validators: Validator[], data: unknown, diff --git a/teams.json b/teams.json index 427e514be9..79bfbe604e 100644 --- a/teams.json +++ b/teams.json @@ -6,6 +6,7 @@ "metamask/assets-controllers": "team-assets", "metamask/base-controller": "team-wallet-framework", "metamask/bridge-controller": "team-swaps,team-bridge", + "metamask/bridge-status-controller": "team-swaps,team-bridge", "metamask/build-utils": "team-wallet-framework", "metamask/composable-controller": "team-wallet-framework", "metamask/controller-utils": "team-wallet-framework", diff --git a/tsconfig.build.json b/tsconfig.build.json index a091abb09e..2894d71c19 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -6,6 +6,8 @@ { "path": "./packages/approval-controller/tsconfig.build.json" }, { "path": "./packages/assets-controllers/tsconfig.build.json" }, { "path": "./packages/base-controller/tsconfig.build.json" }, + { "path": "./packages/bridge-controller/tsconfig.build.json" }, + { "path": "./packages/bridge-status-controller/tsconfig.build.json" }, { "path": "./packages/build-utils/tsconfig.build.json" }, { "path": "./packages/composable-controller/tsconfig.build.json" }, { "path": "./packages/controller-utils/tsconfig.build.json" }, diff --git a/tsconfig.json b/tsconfig.json index 489ba07d2a..1271d8f2ed 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,8 @@ { "path": "./packages/approval-controller" }, { "path": "./packages/assets-controllers" }, { "path": "./packages/base-controller" }, + { "path": "./packages/bridge-controller" }, + { "path": "./packages/bridge-status-controller" }, { "path": "./packages/build-utils" }, { "path": "./packages/composable-controller" }, { "path": "./packages/controller-utils" }, From d5a1256749632bde60842ac868ba1a85d8292ff5 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:17:06 -0500 Subject: [PATCH 10/49] fix: broken tests --- .../src/bridge-status-controller.test.ts | 5 +++++ packages/bridge-status-controller/src/mocks.ts | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index 6e08413ae8..68337c2803 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -74,6 +74,11 @@ const executePollingWithPendingStatus = async () => { }; describe('BridgeStatusController', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.clearAllTimers(); + }); + describe('constructor', () => { it('should setup correctly', () => { const bridgeStatusController = new BridgeStatusController({ diff --git a/packages/bridge-status-controller/src/mocks.ts b/packages/bridge-status-controller/src/mocks.ts index 13ec11607c..a39e6cdb88 100644 --- a/packages/bridge-status-controller/src/mocks.ts +++ b/packages/bridge-status-controller/src/mocks.ts @@ -318,8 +318,14 @@ export const MockTxHistory = { }), targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', initialDestAssetBalance: undefined, - pricingData: { amountSent: '1.234' }, + pricingData: { + amountSent: '1.234', + amountSentInUsd: undefined, + quotedGasInUsd: undefined, + quotedReturnInUsd: undefined, + }, hasApprovalTx: false, + completionTime: undefined, }, }), getComplete: ({ From 4b95d16c19b01276a8e87280a77db8fc2598ba69 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:27:12 -0500 Subject: [PATCH 11/49] chore: remove mocks file to push test coverage up --- .../src/bridge-status-controller.test.ts | 327 +++++++++++++++- .../bridge-status-controller/src/mocks.ts | 358 ------------------ 2 files changed, 322 insertions(+), 363 deletions(-) delete mode 100644 packages/bridge-status-controller/src/mocks.ts diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index 68337c2803..af39bba028 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -1,14 +1,17 @@ import { BridgeClientId } from '@metamask/bridge-controller'; +import type { TransactionMeta } from '@metamask/transaction-controller'; import { numberToHex } from '@metamask/utils'; import BridgeStatusController from './bridge-status-controller'; import { DEFAULT_BRIDGE_STATUS_STATE } from './constants'; -import { - MockStatusResponse, - MockTxHistory, - getMockStartPollingForBridgeTxStatusArgs, -} from './mocks'; import type { BridgeStatusControllerMessenger } from './types'; +import type { + BridgeId, + StatusTypes, + ActionTypes, + StartPollingForBridgeTxStatusArgsSerialized, + BridgeHistoryItem, +} from './types'; import * as bridgeStatusUtils from './utils'; import { flushPromises } from '../../../tests/helpers'; @@ -16,6 +19,320 @@ const EMPTY_INIT_STATE = { bridgeStatusState: { ...DEFAULT_BRIDGE_STATUS_STATE }, }; +const MockStatusResponse = { + getPending: ({ + srcTxHash = '0xsrcTxHash1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + status: 'PENDING' as StatusTypes, + srcChain: { + chainId: srcChainId, + txHash: srcTxHash, + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2518.47', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: destChainId, + token: {}, + }, + }), + getComplete: ({ + srcTxHash = '0xsrcTxHash1', + destTxHash = '0xdestTxHash1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + status: 'COMPLETE' as StatusTypes, + isExpectedToken: true, + bridge: 'across' as BridgeId, + srcChain: { + chainId: srcChainId, + txHash: srcTxHash, + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: destChainId, + txHash: destTxHash, + amount: '990654755978611', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }), +}; + +const getMockQuote = ({ srcChainId = 42161, destChainId = 10 } = {}) => ({ + requestId: '197c402f-cb96-4096-9f8c-54aed84ca776', + srcChainId, + srcTokenAmount: '991250000000000', + srcAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + destChainId, + destTokenAmount: '990654755978612', + destAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + feeData: { + metabridge: { + amount: '8750000000000', + asset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }, + bridgeId: 'lifi', + bridges: ['across'], + steps: [ + { + action: 'bridge' as ActionTypes, + srcChainId, + destChainId, + protocol: { + name: 'across', + displayName: 'Across', + icon: 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png', + }, + srcAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + destAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + srcAmount: '991250000000000', + destAmount: '990654755978612', + }, + ], +}); + +const getMockStartPollingForBridgeTxStatusArgs = ({ + txMetaId = 'bridgeTxMetaId1', + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, +} = {}): StartPollingForBridgeTxStatusArgsSerialized => ({ + bridgeTxMeta: { + id: txMetaId, + } as TransactionMeta, + statusRequest: { + bridgeId: 'lifi', + srcTxHash, + bridge: 'across', + srcChainId, + destChainId, + quote: getMockQuote({ srcChainId, destChainId }), + refuel: false, + }, + quoteResponse: { + quote: getMockQuote({ srcChainId, destChainId }), + trade: { + chainId: srcChainId, + to: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + from: account, + value: '0x038d7ea4c68000', + data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038589602234000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000007f544a44c0000000000000000000000000056ca675c3633cc16bd6849e2b431d4e8de5e23bf000000000000000000000000000000000000000000000000000000000000006c5a39b10a4f4f0747826140d2c5fe6ef47965741f6f7a4734bf784bf3ae3f24520000000a000222266cc2dca0671d2a17ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b7100000000000000000000000000000000000000009ce3c510b3f58edc8d53ae708056e30926f62d0b42d5c9b61c391bb4e8a2c1917f8ed995169ffad0d79af2590303e83c57e15a9e0b248679849556c2e03a1c811b', + gasLimit: 282915, + }, + approval: null, + estimatedProcessingTimeInSeconds: 15, + sentAmount: { amount: '1.234', valueInCurrency: null, usd: null }, + toTokenAmount: { amount: '1.234', valueInCurrency: null, usd: null }, + totalNetworkFee: { amount: '1.234', valueInCurrency: null, usd: null }, + totalMaxNetworkFee: { amount: '1.234', valueInCurrency: null, usd: null }, + gasFee: { amount: '1.234', valueInCurrency: null, usd: null }, + adjustedReturn: { valueInCurrency: null, usd: null }, + swapRate: '1.234', + cost: { valueInCurrency: null, usd: null }, + }, + startTime: 1729964825189, + slippagePercentage: 0, + initialDestAssetBalance: undefined, + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', +}); + +const MockTxHistory = { + getInitNoSrcTxHash: ({ + txMetaId = 'bridgeTxMetaId1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}): Record => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { amountSent: '1.234' }, + status: MockStatusResponse.getPending({ + srcChainId, + }), + hasApprovalTx: false, + }, + }), + getInit: ({ + txMetaId = 'bridgeTxMetaId1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}): Record => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { amountSent: '1.234' }, + status: MockStatusResponse.getPending({ + srcChainId, + }), + hasApprovalTx: false, + }, + }), + getPending: ({ + txMetaId = 'bridgeTxMetaId1', + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}): Record => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + status: MockStatusResponse.getPending({ + srcTxHash, + srcChainId, + }), + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { + amountSent: '1.234', + amountSentInUsd: undefined, + quotedGasInUsd: undefined, + quotedReturnInUsd: undefined, + }, + hasApprovalTx: false, + completionTime: undefined, + }, + }), + getComplete: ({ + txMetaId = 'bridgeTxMetaId1', + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}): Record => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + completionTime: 1736277625746, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + status: MockStatusResponse.getComplete({ srcTxHash }), + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { + amountSent: '1.234', + amountSentInUsd: undefined, + quotedGasInUsd: undefined, + quotedReturnInUsd: undefined, + }, + hasApprovalTx: false, + }, + }), +}; + const getMessengerMock = ({ account = '0xaccount1', srcChainId = 42161, diff --git a/packages/bridge-status-controller/src/mocks.ts b/packages/bridge-status-controller/src/mocks.ts deleted file mode 100644 index a39e6cdb88..0000000000 --- a/packages/bridge-status-controller/src/mocks.ts +++ /dev/null @@ -1,358 +0,0 @@ -import type { TransactionMeta } from '@metamask/transaction-controller'; - -import type { - BridgeId, - StatusResponse, - StatusTypes, - ActionTypes, - StartPollingForBridgeTxStatusArgsSerialized, - BridgeHistoryItem, -} from './types'; - -export const MockStatusResponse = { - getPending: ({ - srcTxHash = '0xsrcTxHash1', - srcChainId = 42161, - destChainId = 10, - } = {}) => ({ - status: 'PENDING' as StatusTypes, - srcChain: { - chainId: srcChainId, - txHash: srcTxHash, - amount: '991250000000000', - token: { - address: '0x0000000000000000000000000000000000000000', - chainId: srcChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2518.47', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - }, - destChain: { - chainId: destChainId, - token: {}, - }, - }), - getComplete: ({ - srcTxHash = '0xsrcTxHash1', - destTxHash = '0xdestTxHash1', - srcChainId = 42161, - destChainId = 10, - } = {}) => ({ - status: 'COMPLETE' as StatusTypes, - isExpectedToken: true, - bridge: 'across' as BridgeId, - srcChain: { - chainId: srcChainId, - txHash: srcTxHash, - amount: '991250000000000', - token: { - address: '0x0000000000000000000000000000000000000000', - chainId: srcChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.7', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - }, - destChain: { - chainId: destChainId, - txHash: destTxHash, - amount: '990654755978611', - token: { - address: '0x0000000000000000000000000000000000000000', - chainId: destChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.63', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - }, - }), -}; - -let mockFetchBridgeTxStatusCount = 0; -export const mockFetchBridgeTxStatus: () => Promise = () => { - return new Promise((resolve) => { - console.log('HELLO mockFetchBridgeTxStatus', mockFetchBridgeTxStatusCount); - setTimeout(() => { - if (mockFetchBridgeTxStatusCount === 0) { - resolve( - MockStatusResponse.getPending({ - srcTxHash: - '0xe3e223b9725765a7de557effdb2b507ace3534bcff2c1fe3a857e0791e56a518', - srcChainId: 1, - destChainId: 42161, - }), - ); - } else { - resolve( - MockStatusResponse.getComplete({ - srcTxHash: - '0xe3e223b9725765a7de557effdb2b507ace3534bcff2c1fe3a857e0791e56a518', - destTxHash: - '0x010e1bffe8288956012e6b6132d7eb3eaf9d0bbf066bd13aae13b973c678508f', - srcChainId: 1, - destChainId: 42161, - }), - ); - } - mockFetchBridgeTxStatusCount += 1; - }, 2000); - }); -}; - -export const getMockQuote = ({ - srcChainId = 42161, - destChainId = 10, -} = {}) => ({ - requestId: '197c402f-cb96-4096-9f8c-54aed84ca776', - srcChainId, - srcTokenAmount: '991250000000000', - srcAsset: { - address: '0x0000000000000000000000000000000000000000', - chainId: srcChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.7', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - destChainId, - destTokenAmount: '990654755978612', - destAsset: { - address: '0x0000000000000000000000000000000000000000', - chainId: destChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.63', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - feeData: { - metabridge: { - amount: '8750000000000', - asset: { - address: '0x0000000000000000000000000000000000000000', - chainId: srcChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.7', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - }, - }, - bridgeId: 'lifi', - bridges: ['across'], - steps: [ - { - action: 'bridge' as ActionTypes, - srcChainId, - destChainId, - protocol: { - name: 'across', - displayName: 'Across', - icon: 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png', - }, - srcAsset: { - address: '0x0000000000000000000000000000000000000000', - chainId: srcChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.7', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - destAsset: { - address: '0x0000000000000000000000000000000000000000', - chainId: destChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.63', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - srcAmount: '991250000000000', - destAmount: '990654755978612', - }, - ], -}); - -export const getMockStartPollingForBridgeTxStatusArgs = ({ - txMetaId = 'bridgeTxMetaId1', - srcTxHash = '0xsrcTxHash1', - account = '0xaccount1', - srcChainId = 42161, - destChainId = 10, -} = {}): StartPollingForBridgeTxStatusArgsSerialized => ({ - bridgeTxMeta: { - id: txMetaId, - } as TransactionMeta, - statusRequest: { - bridgeId: 'lifi', - srcTxHash, - bridge: 'across', - srcChainId, - destChainId, - quote: getMockQuote({ srcChainId, destChainId }), - refuel: false, - }, - quoteResponse: { - quote: getMockQuote({ srcChainId, destChainId }), - trade: { - chainId: srcChainId, - to: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', - from: account, - value: '0x038d7ea4c68000', - data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038589602234000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000007f544a44c0000000000000000000000000056ca675c3633cc16bd6849e2b431d4e8de5e23bf000000000000000000000000000000000000000000000000000000000000006c5a39b10a4f4f0747826140d2c5fe6ef47965741f6f7a4734bf784bf3ae3f24520000000a000222266cc2dca0671d2a17ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b7100000000000000000000000000000000000000009ce3c510b3f58edc8d53ae708056e30926f62d0b42d5c9b61c391bb4e8a2c1917f8ed995169ffad0d79af2590303e83c57e15a9e0b248679849556c2e03a1c811b', - gasLimit: 282915, - }, - approval: null, - estimatedProcessingTimeInSeconds: 15, - sentAmount: { amount: '1.234', valueInCurrency: null, usd: null }, - toTokenAmount: { amount: '1.234', valueInCurrency: null, usd: null }, - totalNetworkFee: { amount: '1.234', valueInCurrency: null, usd: null }, - totalMaxNetworkFee: { amount: '1.234', valueInCurrency: null, usd: null }, - gasFee: { amount: '1.234', valueInCurrency: null, usd: null }, - adjustedReturn: { valueInCurrency: null, usd: null }, - swapRate: '1.234', - cost: { valueInCurrency: null, usd: null }, - }, - startTime: 1729964825189, - slippagePercentage: 0, - initialDestAssetBalance: undefined, - targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', -}); - -export const MockTxHistory = { - getInitNoSrcTxHash: ({ - txMetaId = 'bridgeTxMetaId1', - account = '0xaccount1', - srcChainId = 42161, - destChainId = 10, - } = {}): Record => ({ - [txMetaId]: { - txMetaId, - quote: getMockQuote({ srcChainId, destChainId }), - startTime: 1729964825189, - estimatedProcessingTimeInSeconds: 15, - slippagePercentage: 0, - account, - targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', - initialDestAssetBalance: undefined, - pricingData: { amountSent: '1.234' }, - status: MockStatusResponse.getPending({ - srcChainId, - }), - hasApprovalTx: false, - }, - }), - getInit: ({ - txMetaId = 'bridgeTxMetaId1', - account = '0xaccount1', - srcChainId = 42161, - destChainId = 10, - } = {}): Record => ({ - [txMetaId]: { - txMetaId, - quote: getMockQuote({ srcChainId, destChainId }), - startTime: 1729964825189, - estimatedProcessingTimeInSeconds: 15, - slippagePercentage: 0, - account, - targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', - initialDestAssetBalance: undefined, - pricingData: { amountSent: '1.234' }, - status: MockStatusResponse.getPending({ - srcChainId, - }), - hasApprovalTx: false, - }, - }), - getPending: ({ - txMetaId = 'bridgeTxMetaId1', - srcTxHash = '0xsrcTxHash1', - account = '0xaccount1', - srcChainId = 42161, - destChainId = 10, - } = {}): Record => ({ - [txMetaId]: { - txMetaId, - quote: getMockQuote({ srcChainId, destChainId }), - startTime: 1729964825189, - estimatedProcessingTimeInSeconds: 15, - slippagePercentage: 0, - account, - status: MockStatusResponse.getPending({ - srcTxHash, - srcChainId, - }), - targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', - initialDestAssetBalance: undefined, - pricingData: { - amountSent: '1.234', - amountSentInUsd: undefined, - quotedGasInUsd: undefined, - quotedReturnInUsd: undefined, - }, - hasApprovalTx: false, - completionTime: undefined, - }, - }), - getComplete: ({ - txMetaId = 'bridgeTxMetaId1', - srcTxHash = '0xsrcTxHash1', - account = '0xaccount1', - srcChainId = 42161, - destChainId = 10, - } = {}): Record => ({ - [txMetaId]: { - txMetaId, - quote: getMockQuote({ srcChainId, destChainId }), - startTime: 1729964825189, - completionTime: 1736277625746, - estimatedProcessingTimeInSeconds: 15, - slippagePercentage: 0, - account, - status: MockStatusResponse.getComplete({ srcTxHash }), - targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', - initialDestAssetBalance: undefined, - pricingData: { - amountSent: '1.234', - amountSentInUsd: undefined, - quotedGasInUsd: undefined, - quotedReturnInUsd: undefined, - }, - hasApprovalTx: false, - }, - }), -}; From 00fd19c334f82e6847ca60a929d73e4b8246067b Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:44:30 -0500 Subject: [PATCH 12/49] feat: add fetchBridgeTxStatus test suite and export FeeType --- packages/bridge-controller/src/index.ts | 3 +- .../src/utils.test.ts | 136 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 packages/bridge-status-controller/src/utils.test.ts diff --git a/packages/bridge-controller/src/index.ts b/packages/bridge-controller/src/index.ts index 415682821f..9f321d5142 100644 --- a/packages/bridge-controller/src/index.ts +++ b/packages/bridge-controller/src/index.ts @@ -19,7 +19,6 @@ export type { Quote, QuoteResponse, ChainId, - FeeType, FeeData, TxData, BridgeFeatureFlagsKey, @@ -34,6 +33,8 @@ export type { BridgeControllerMessenger, } from './types'; +export { FeeType } from './types'; + export { ALLOWED_BRIDGE_CHAIN_IDS, BridgeClientId, diff --git a/packages/bridge-status-controller/src/utils.test.ts b/packages/bridge-status-controller/src/utils.test.ts new file mode 100644 index 0000000000..5ccf980f65 --- /dev/null +++ b/packages/bridge-status-controller/src/utils.test.ts @@ -0,0 +1,136 @@ +import { BridgeClientId, FeeType } from '@metamask/bridge-controller'; + +import type { StatusRequestWithSrcTxHash, FetchFunction } from './types'; +import { fetchBridgeTxStatus, BRIDGE_STATUS_BASE_URL } from './utils'; + +describe('fetchBridgeTxStatus', () => { + const mockClientId = BridgeClientId.EXTENSION; + + const mockStatusRequest: StatusRequestWithSrcTxHash = { + bridgeId: 'socket', + srcTxHash: '0x123', + bridge: 'socket', + srcChainId: 1, + destChainId: 137, + refuel: false, + quote: { + requestId: 'req-123', + bridgeId: 'socket', + bridges: ['socket'], + srcChainId: 1, + destChainId: 137, + srcAsset: { + chainId: 1, + address: '0x123', + symbol: 'ETH', + name: 'Ether', + decimals: 18, + icon: undefined, + }, + srcTokenAmount: '', + destAsset: { + chainId: 137, + address: '0x456', + symbol: 'USDC', + name: 'USD Coin', + decimals: 6, + icon: undefined, + }, + destTokenAmount: '', + feeData: { + [FeeType.METABRIDGE]: { + amount: '100', + asset: { + chainId: 1, + address: '0x123', + symbol: 'ETH', + name: 'Ether', + decimals: 18, + icon: 'eth.jpeg', + }, + }, + }, + steps: [], + }, + }; + + const mockValidResponse = { + status: 'PENDING', + srcChain: { + chainId: 1, + txHash: '0x123', + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 1, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2518.47', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: 137, + token: {}, + }, + }; + + it('should successfully fetch and validate bridge transaction status', async () => { + const mockFetch: FetchFunction = jest + .fn() + .mockResolvedValue(mockValidResponse); + + const result = await fetchBridgeTxStatus( + mockStatusRequest, + mockClientId, + mockFetch, + ); + + // Verify the fetch was called with correct parameters + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining(BRIDGE_STATUS_BASE_URL), + { + headers: { 'X-Client-Id': mockClientId }, + }, + ); + + // Verify URL contains all required parameters + const callUrl = (mockFetch as jest.Mock).mock.calls[0][0]; + expect(callUrl).toContain(`bridgeId=${mockStatusRequest.bridgeId}`); + expect(callUrl).toContain(`srcTxHash=${mockStatusRequest.srcTxHash}`); + expect(callUrl).toContain( + `requestId=${mockStatusRequest.quote?.requestId}`, + ); + + // Verify response + expect(result).toStrictEqual(mockValidResponse); + }); + + it('should throw error when response validation fails', async () => { + const invalidResponse = { + invalid: 'response', + }; + + const mockFetch: FetchFunction = jest + .fn() + .mockResolvedValue(invalidResponse); + + await expect( + fetchBridgeTxStatus(mockStatusRequest, mockClientId, mockFetch), + ).rejects.toThrow('Invalid response from bridge'); + }); + + it('should handle fetch errors', async () => { + const mockFetch: FetchFunction = jest + .fn() + .mockRejectedValue(new Error('Network error')); + + await expect( + fetchBridgeTxStatus(mockStatusRequest, mockClientId, mockFetch), + ).rejects.toThrow('Network error'); + }); +}); From c3556e23d4d2a382b5a1dd8ebe6612b4f6616c0b Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:52:22 -0500 Subject: [PATCH 13/49] test: add getStatusRequestDto tests and refactor utils test suite --- .../src/utils.test.ts | 143 +++++++++++------- 1 file changed, 92 insertions(+), 51 deletions(-) diff --git a/packages/bridge-status-controller/src/utils.test.ts b/packages/bridge-status-controller/src/utils.test.ts index 5ccf980f65..a4032c903e 100644 --- a/packages/bridge-status-controller/src/utils.test.ts +++ b/packages/bridge-status-controller/src/utils.test.ts @@ -1,11 +1,13 @@ import { BridgeClientId, FeeType } from '@metamask/bridge-controller'; import type { StatusRequestWithSrcTxHash, FetchFunction } from './types'; -import { fetchBridgeTxStatus, BRIDGE_STATUS_BASE_URL } from './utils'; - -describe('fetchBridgeTxStatus', () => { - const mockClientId = BridgeClientId.EXTENSION; +import { + fetchBridgeTxStatus, + BRIDGE_STATUS_BASE_URL, + getStatusRequestDto, +} from './utils'; +describe('utils', () => { const mockStatusRequest: StatusRequestWithSrcTxHash = { bridgeId: 'socket', srcTxHash: '0x123', @@ -79,58 +81,97 @@ describe('fetchBridgeTxStatus', () => { }, }; - it('should successfully fetch and validate bridge transaction status', async () => { - const mockFetch: FetchFunction = jest - .fn() - .mockResolvedValue(mockValidResponse); - - const result = await fetchBridgeTxStatus( - mockStatusRequest, - mockClientId, - mockFetch, - ); - - // Verify the fetch was called with correct parameters - expect(mockFetch).toHaveBeenCalledWith( - expect.stringContaining(BRIDGE_STATUS_BASE_URL), - { - headers: { 'X-Client-Id': mockClientId }, - }, - ); - - // Verify URL contains all required parameters - const callUrl = (mockFetch as jest.Mock).mock.calls[0][0]; - expect(callUrl).toContain(`bridgeId=${mockStatusRequest.bridgeId}`); - expect(callUrl).toContain(`srcTxHash=${mockStatusRequest.srcTxHash}`); - expect(callUrl).toContain( - `requestId=${mockStatusRequest.quote?.requestId}`, - ); - - // Verify response - expect(result).toStrictEqual(mockValidResponse); - }); + describe('fetchBridgeTxStatus', () => { + const mockClientId = BridgeClientId.EXTENSION; + + it('should successfully fetch and validate bridge transaction status', async () => { + const mockFetch: FetchFunction = jest + .fn() + .mockResolvedValue(mockValidResponse); + + const result = await fetchBridgeTxStatus( + mockStatusRequest, + mockClientId, + mockFetch, + ); + + // Verify the fetch was called with correct parameters + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining(BRIDGE_STATUS_BASE_URL), + { + headers: { 'X-Client-Id': mockClientId }, + }, + ); - it('should throw error when response validation fails', async () => { - const invalidResponse = { - invalid: 'response', - }; + // Verify URL contains all required parameters + const callUrl = (mockFetch as jest.Mock).mock.calls[0][0]; + expect(callUrl).toContain(`bridgeId=${mockStatusRequest.bridgeId}`); + expect(callUrl).toContain(`srcTxHash=${mockStatusRequest.srcTxHash}`); + expect(callUrl).toContain( + `requestId=${mockStatusRequest.quote?.requestId}`, + ); - const mockFetch: FetchFunction = jest - .fn() - .mockResolvedValue(invalidResponse); + // Verify response + expect(result).toStrictEqual(mockValidResponse); + }); - await expect( - fetchBridgeTxStatus(mockStatusRequest, mockClientId, mockFetch), - ).rejects.toThrow('Invalid response from bridge'); + it('should throw error when response validation fails', async () => { + const invalidResponse = { + invalid: 'response', + }; + + const mockFetch: FetchFunction = jest + .fn() + .mockResolvedValue(invalidResponse); + + await expect( + fetchBridgeTxStatus(mockStatusRequest, mockClientId, mockFetch), + ).rejects.toThrow('Invalid response from bridge'); + }); + + it('should handle fetch errors', async () => { + const mockFetch: FetchFunction = jest + .fn() + .mockRejectedValue(new Error('Network error')); + + await expect( + fetchBridgeTxStatus(mockStatusRequest, mockClientId, mockFetch), + ).rejects.toThrow('Network error'); + }); }); - it('should handle fetch errors', async () => { - const mockFetch: FetchFunction = jest - .fn() - .mockRejectedValue(new Error('Network error')); + describe('getStatusRequestDto', () => { + it('should handle status request with quote', () => { + const result = getStatusRequestDto(mockStatusRequest); + + expect(result).toStrictEqual({ + bridgeId: 'socket', + srcTxHash: '0x123', + bridge: 'socket', + srcChainId: '1', + destChainId: '137', + refuel: 'false', + requestId: 'req-123', + }); + }); + + it('should handle status request without quote', () => { + const statusRequestWithoutQuote = { + ...mockStatusRequest, + quote: undefined, + }; + + const result = getStatusRequestDto(statusRequestWithoutQuote); - await expect( - fetchBridgeTxStatus(mockStatusRequest, mockClientId, mockFetch), - ).rejects.toThrow('Network error'); + expect(result).toStrictEqual({ + bridgeId: 'socket', + srcTxHash: '0x123', + bridge: 'socket', + srcChainId: '1', + destChainId: '137', + refuel: 'false', + }); + expect(result).not.toHaveProperty('requestId'); + }); }); }); From e3d1a35689fc2e3ba0316943f47b216c373d0a47 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:00:14 -0500 Subject: [PATCH 14/49] test: add test for polling with no source transaction hash --- .../src/bridge-status-controller.test.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index af39bba028..988e9859a0 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -527,6 +527,41 @@ describe('BridgeStatusController', () => { jest.restoreAllMocks(); }); + it('does not poll if the srcTxHash is not available', async () => { + // Setup + jest.useFakeTimers(); + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), + }); + const fetchBridgeTxStatusSpy = jest.spyOn( + bridgeStatusUtils, + 'fetchBridgeTxStatus', + ); + + // Start polling with args that have no srcTxHash + const startPollingArgs = getMockStartPollingForBridgeTxStatusArgs(); + startPollingArgs.statusRequest.srcTxHash = undefined; + bridgeStatusController.startPollingForBridgeTxStatus(startPollingArgs); + + // Advance timer to trigger polling + jest.advanceTimersByTime(10000); + await flushPromises(); + + // Assertions + expect(fetchBridgeTxStatusSpy).not.toHaveBeenCalled(); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toHaveProperty('bridgeTxMetaId1'); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 + .status.srcChain.txHash, + ).toBeUndefined(); + + // Cleanup + jest.restoreAllMocks(); + }); }); describe('resetState', () => { it('resets the state', async () => { From 8ebffe567c82001881788b4ce313b13e364576ce Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:47:47 -0500 Subject: [PATCH 15/49] test: enhance startPollingForBridgeTxStatus test cases for not polling when no srcTxHash --- .../src/bridge-status-controller.test.ts | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index 988e9859a0..1e144709f9 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable jest/no-conditional-in-test */ import { BridgeClientId } from '@metamask/bridge-controller'; import type { TransactionMeta } from '@metamask/transaction-controller'; import { numberToHex } from '@metamask/utils'; @@ -336,6 +337,8 @@ const MockTxHistory = { const getMessengerMock = ({ account = '0xaccount1', srcChainId = 42161, + txHash = '0xsrcTxHash1', + txMetaId = 'bridgeTxMetaId1', } = {}) => ({ call: jest.fn((method: string) => { @@ -351,6 +354,15 @@ const getMessengerMock = ({ chainId: numberToHex(srcChainId), }, }; + } else if (method === 'TransactionController:getState') { + return { + transactions: [ + { + id: txMetaId, + hash: txHash, + }, + ], + }; } return null; }), @@ -492,7 +504,6 @@ describe('BridgeStatusController', () => { // Setup jest.useFakeTimers(); jest.spyOn(Date, 'now').mockImplementation(() => { - // eslint-disable-next-line jest/no-conditional-in-test return MockTxHistory.getComplete().bridgeTxMetaId1.completionTime ?? 10; }); const bridgeStatusController = new BridgeStatusController({ @@ -530,8 +541,42 @@ describe('BridgeStatusController', () => { it('does not poll if the srcTxHash is not available', async () => { // Setup jest.useFakeTimers(); + + const messengerMock = { + call: jest.fn((method: string) => { + if (method === 'AccountsController:getSelectedAccount') { + return { address: '0xaccount1' }; + } else if ( + method === 'NetworkController:findNetworkClientIdByChainId' + ) { + return 'networkClientId'; + } else if (method === 'NetworkController:getState') { + return { selectedNetworkClientId: 'networkClientId' }; + } else if (method === 'NetworkController:getNetworkClientById') { + return { + configuration: { + chainId: numberToHex(42161), + }, + }; + } else if (method === 'TransactionController:getState') { + return { + transactions: [ + { + id: 'bridgeTxMetaId1', + hash: undefined, + }, + ], + }; + } + return null; + }), + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as unknown as jest.Mocked; + const bridgeStatusController = new BridgeStatusController({ - messenger: getMessengerMock(), + messenger: messengerMock, clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), }); @@ -585,10 +630,9 @@ describe('BridgeStatusController', () => { let getSelectedAccountCalledTimes = 0; const messengerMock = { call: jest.fn((method: string) => { - // eslint-disable-next-line jest/no-conditional-in-test if (method === 'AccountsController:getSelectedAccount') { let account; - // eslint-disable-next-line jest/no-conditional-in-test + if (getSelectedAccountCalledTimes === 0) { account = '0xaccount1'; } else { @@ -596,15 +640,12 @@ describe('BridgeStatusController', () => { } getSelectedAccountCalledTimes += 1; return { address: account }; - // eslint-disable-next-line jest/no-conditional-in-test } else if ( method === 'NetworkController:findNetworkClientIdByChainId' ) { return 'networkClientId'; - // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getState') { return { selectedNetworkClientId: 'networkClientId' }; - // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getNetworkClientById') { return { configuration: { @@ -679,18 +720,14 @@ describe('BridgeStatusController', () => { jest.useFakeTimers(); const messengerMock = { call: jest.fn((method: string) => { - // eslint-disable-next-line jest/no-conditional-in-test if (method === 'AccountsController:getSelectedAccount') { return { address: '0xaccount1' }; - // eslint-disable-next-line jest/no-conditional-in-test } else if ( method === 'NetworkController:findNetworkClientIdByChainId' ) { return 'networkClientId'; - // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getState') { return { selectedNetworkClientId: 'networkClientId' }; - // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getNetworkClientById') { return { configuration: { @@ -781,18 +818,14 @@ describe('BridgeStatusController', () => { jest.useFakeTimers(); const messengerMock = { call: jest.fn((method: string) => { - // eslint-disable-next-line jest/no-conditional-in-test if (method === 'AccountsController:getSelectedAccount') { return { address: '0xaccount1' }; - // eslint-disable-next-line jest/no-conditional-in-test } else if ( method === 'NetworkController:findNetworkClientIdByChainId' ) { return 'networkClientId'; - // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getState') { return { selectedNetworkClientId: 'networkClientId' }; - // eslint-disable-next-line jest/no-conditional-in-test } else if (method === 'NetworkController:getNetworkClientById') { return { configuration: { From 5ba5372a4324e0e0aa3d7ef8eae74078c3566ab3 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:54:39 -0500 Subject: [PATCH 16/49] test: adjust coverage thresholds for bridge status controller --- packages/bridge-status-controller/jest.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bridge-status-controller/jest.config.js b/packages/bridge-status-controller/jest.config.js index c8fc07a065..627a0cb4f8 100644 --- a/packages/bridge-status-controller/jest.config.js +++ b/packages/bridge-status-controller/jest.config.js @@ -17,8 +17,8 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 89, - functions: 98, + branches: 98, + functions: 93, lines: 98, statements: 98, }, From a038f088595f8bc349eb312f4b24a5c1c3281056 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:59:40 -0500 Subject: [PATCH 17/49] test: update coverage thresholds for bridge status controller --- packages/bridge-status-controller/jest.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bridge-status-controller/jest.config.js b/packages/bridge-status-controller/jest.config.js index 627a0cb4f8..5307567fa5 100644 --- a/packages/bridge-status-controller/jest.config.js +++ b/packages/bridge-status-controller/jest.config.js @@ -17,8 +17,8 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 98, - functions: 93, + branches: 93, + functions: 98, lines: 98, statements: 98, }, From 1d5c5b728a67fb9b42e2ddc0455c8b3f07d20be0 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:01:23 -0500 Subject: [PATCH 18/49] test: add test cases for bridge transaction complete and failed events --- .../src/bridge-status-controller.test.ts | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index 1e144709f9..c0d8fe5181 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -93,6 +93,34 @@ const MockStatusResponse = { }, }, }), + getFailed: ({ + srcTxHash = '0xsrcTxHash1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + status: 'FAILED' as StatusTypes, + srcChain: { + chainId: srcChainId, + txHash: srcTxHash, + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2518.47', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: destChainId, + token: {}, + }, + }), }; const getMockQuote = ({ srcChainId = 42161, destChainId = 10 } = {}) => ({ @@ -536,6 +564,7 @@ describe('BridgeStatusController', () => { bridgeStatusController.state.bridgeStatusState.txHistory, ).toStrictEqual(MockTxHistory.getComplete()); + // Cleanup jest.restoreAllMocks(); }); it('does not poll if the srcTxHash is not available', async () => { @@ -604,6 +633,94 @@ describe('BridgeStatusController', () => { .status.srcChain.txHash, ).toBeUndefined(); + // Cleanup + jest.restoreAllMocks(); + }); + it('emits bridgeTransactionComplete event when the status response is complete', async () => { + // Setup + jest.useFakeTimers(); + jest.spyOn(Date, 'now').mockImplementation(() => { + return MockTxHistory.getComplete().bridgeTxMetaId1.completionTime ?? 10; + }); + + const messengerMock = getMessengerMock(); + const bridgeStatusController = new BridgeStatusController({ + messenger: messengerMock, + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), + }); + + const fetchBridgeTxStatusSpy = jest + .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete(); + }); + + // Execution + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + jest.advanceTimersByTime(10000); + await flushPromises(); + + // Assertions + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + expect(messengerMock.publish).toHaveBeenCalledWith( + 'BridgeStatusController:bridgeTransactionComplete', + { + bridgeHistoryItem: expect.objectContaining({ + txMetaId: 'bridgeTxMetaId1', + status: expect.objectContaining({ + status: 'COMPLETE', + }), + }), + }, + ); + + // Cleanup + jest.restoreAllMocks(); + }); + it('emits bridgeTransactionFailed event when the status response is failed', async () => { + // Setup + jest.useFakeTimers(); + jest.spyOn(Date, 'now').mockImplementation(() => { + return MockTxHistory.getComplete().bridgeTxMetaId1.completionTime ?? 10; + }); + + const messengerMock = getMessengerMock(); + const bridgeStatusController = new BridgeStatusController({ + messenger: messengerMock, + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), + }); + + const fetchBridgeTxStatusSpy = jest + .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') + .mockImplementationOnce(async () => { + return MockStatusResponse.getFailed(); + }); + + // Execution + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + jest.advanceTimersByTime(10000); + await flushPromises(); + + // Assertions + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + expect(messengerMock.publish).toHaveBeenCalledWith( + 'BridgeStatusController:bridgeTransactionFailed', + { + bridgeHistoryItem: expect.objectContaining({ + txMetaId: 'bridgeTxMetaId1', + status: expect.objectContaining({ + status: 'FAILED', + }), + }), + }, + ); + // Cleanup jest.restoreAllMocks(); }); From cbde55a550c44adccb9f9f82170cbdd99d361183 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:06:00 -0500 Subject: [PATCH 19/49] test: add test case for updating srcTxHash during polling --- .../src/bridge-status-controller.test.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index c0d8fe5181..76d314fd5b 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -721,6 +721,75 @@ describe('BridgeStatusController', () => { }, ); + // Cleanup + jest.restoreAllMocks(); + }); + it('updates the srcTxHash when one is available', async () => { + // Setup + jest.useFakeTimers(); + let getStateCallCount = 0; + + const messengerMock = { + call: jest.fn((method: string) => { + if (method === 'AccountsController:getSelectedAccount') { + return { address: '0xaccount1' }; + } else if ( + method === 'NetworkController:findNetworkClientIdByChainId' + ) { + return 'networkClientId'; + } else if (method === 'NetworkController:getState') { + return { selectedNetworkClientId: 'networkClientId' }; + } else if (method === 'NetworkController:getNetworkClientById') { + return { + configuration: { + chainId: numberToHex(42161), + }, + }; + } else if (method === 'TransactionController:getState') { + getStateCallCount += 1; + return { + transactions: [ + { + id: 'bridgeTxMetaId1', + hash: getStateCallCount === 0 ? undefined : '0xnewTxHash', + }, + ], + }; + } + return null; + }), + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as unknown as jest.Mocked; + + const bridgeStatusController = new BridgeStatusController({ + messenger: messengerMock, + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), + }); + + // Start polling with no srcTxHash + const startPollingArgs = getMockStartPollingForBridgeTxStatusArgs(); + startPollingArgs.statusRequest.srcTxHash = undefined; + bridgeStatusController.startPollingForBridgeTxStatus(startPollingArgs); + + // Verify initial state has no srcTxHash + expect( + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 + .status.srcChain.txHash, + ).toBeUndefined(); + + // Advance timer to trigger polling with new hash + jest.advanceTimersByTime(10000); + await flushPromises(); + + // Verify the srcTxHash was updated + expect( + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 + .status.srcChain.txHash, + ).toBe('0xnewTxHash'); + // Cleanup jest.restoreAllMocks(); }); From 454e2229508d9e9353b8ecc7291852ecb2fe8ee5 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:09:34 -0500 Subject: [PATCH 20/49] refactor: simplify bridge status state updates --- .../src/bridge-status-controller.ts | 39 ++++--------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 21b4817b63..d40ce5282c 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -199,15 +199,9 @@ export default class BridgeStatusController extends StaticIntervalPollingControl }, hasApprovalTx: Boolean(quoteResponse.approval), }; - this.update((_state) => { - _state.bridgeStatusState = { - ...bridgeStatusState, - txHistory: { - ...bridgeStatusState.txHistory, - // Use the txMeta.id as the key so we can reference the txMeta in TransactionController - [bridgeTxMeta.id]: txHistoryItem, - }, - }; + this.update((state) => { + // Use the txMeta.id as the key so we can reference the txMeta in TransactionController + state.bridgeStatusState.txHistory[bridgeTxMeta.id] = txHistoryItem; }); this.#pollingTokensByTxMetaId[bridgeTxMeta.id] = this.startPolling({ @@ -266,13 +260,8 @@ export default class BridgeStatusController extends StaticIntervalPollingControl // we need to keep track of the account that this is associated with as well so that we don't show it in Activity list for other accounts // First stab at this will not stop polling when you are on a different account this.update((state) => { - state.bridgeStatusState = { - ...bridgeStatusState, - txHistory: { - ...bridgeStatusState.txHistory, - [bridgeTxMetaId]: newBridgeHistoryItem, - }, - }; + state.bridgeStatusState.txHistory[bridgeTxMetaId] = + newBridgeHistoryItem; }); const pollingToken = this.#pollingTokensByTxMetaId[bridgeTxMetaId]; @@ -330,22 +319,8 @@ export default class BridgeStatusController extends StaticIntervalPollingControl } this.update((state) => { - state.bridgeStatusState = { - ...bridgeStatusState, - txHistory: { - ...bridgeStatusState.txHistory, - [bridgeTxMetaId]: { - ...bridgeStatusState.txHistory[bridgeTxMetaId], - status: { - ...bridgeStatusState.txHistory[bridgeTxMetaId].status, - srcChain: { - ...bridgeStatusState.txHistory[bridgeTxMetaId].status.srcChain, - txHash: srcTxHash, - }, - }, - }, - }, - }; + state.bridgeStatusState.txHistory[bridgeTxMetaId].status.srcChain.txHash = + srcTxHash; }); }; From 9a35a20340b6097a299762a72e64c1ad4fa50b1d Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:10:22 -0500 Subject: [PATCH 21/49] chore: increase coverage thresholds for bridge status controller --- packages/bridge-status-controller/jest.config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bridge-status-controller/jest.config.js b/packages/bridge-status-controller/jest.config.js index 5307567fa5..15a04af42e 100644 --- a/packages/bridge-status-controller/jest.config.js +++ b/packages/bridge-status-controller/jest.config.js @@ -17,10 +17,10 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 93, - functions: 98, - lines: 98, - statements: 98, + branches: 94, + functions: 100, + lines: 100, + statements: 100, }, }, }); From 45c0e21acc7e9cc3e2cc5159eab60ffc4adbeb92 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:11:29 -0500 Subject: [PATCH 22/49] refactor: remove unused bridgeStatusState variable --- .../bridge-status-controller/src/bridge-status-controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index d40ce5282c..353e98384e 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -167,7 +167,6 @@ export default class BridgeStatusController extends StaticIntervalPollingControl initialDestAssetBalance, targetContractAddress, } = startPollingForBridgeTxStatusArgs; - const { bridgeStatusState } = this.state; const { address: account } = this.#getSelectedAccount(); // Write all non-status fields to state so we can reference the quote in Activity list without the Bridge API From 0551f692f9df304e55c792d39d8c98fda8c419a5 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:16:24 -0500 Subject: [PATCH 23/49] chore: commit snapshots --- .../bridge-status-controller.test.ts.snap | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap diff --git a/packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap b/packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap new file mode 100644 index 0000000000..fdf64b3dbd --- /dev/null +++ b/packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap @@ -0,0 +1,227 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BridgeStatusController constructor rehydrates the tx history state 1`] = ` +Object { + "bridgeTxMetaId1": Object { + "account": "0xaccount1", + "estimatedProcessingTimeInSeconds": 15, + "hasApprovalTx": false, + "initialDestAssetBalance": undefined, + "pricingData": Object { + "amountSent": "1.234", + "amountSentInUsd": undefined, + "quotedGasInUsd": undefined, + "quotedReturnInUsd": undefined, + }, + "quote": Object { + "bridgeId": "lifi", + "bridges": Array [ + "across", + ], + "destAsset": Object { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 10, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.63", + "symbol": "ETH", + }, + "destChainId": 10, + "destTokenAmount": "990654755978612", + "feeData": Object { + "metabridge": Object { + "amount": "8750000000000", + "asset": Object { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + }, + }, + "requestId": "197c402f-cb96-4096-9f8c-54aed84ca776", + "srcAsset": Object { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + "srcChainId": 42161, + "srcTokenAmount": "991250000000000", + "steps": Array [ + Object { + "action": "bridge", + "destAmount": "990654755978612", + "destAsset": Object { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 10, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.63", + "symbol": "ETH", + }, + "destChainId": 10, + "protocol": Object { + "displayName": "Across", + "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png", + "name": "across", + }, + "srcAmount": "991250000000000", + "srcAsset": Object { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + "srcChainId": 42161, + }, + ], + }, + "slippagePercentage": 0, + "startTime": 1729964825189, + "status": Object { + "srcChain": Object { + "chainId": 42161, + "txHash": "0xsrcTxHash1", + }, + "status": "PENDING", + }, + "targetContractAddress": "0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC", + "txMetaId": "bridgeTxMetaId1", + }, +} +`; + +exports[`BridgeStatusController startPollingForBridgeTxStatus sets the inital tx history state 1`] = ` +Object { + "bridgeTxMetaId1": Object { + "account": "0xaccount1", + "estimatedProcessingTimeInSeconds": 15, + "hasApprovalTx": false, + "initialDestAssetBalance": undefined, + "pricingData": Object { + "amountSent": "1.234", + "amountSentInUsd": undefined, + "quotedGasInUsd": undefined, + "quotedReturnInUsd": undefined, + }, + "quote": Object { + "bridgeId": "lifi", + "bridges": Array [ + "across", + ], + "destAsset": Object { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 10, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.63", + "symbol": "ETH", + }, + "destChainId": 10, + "destTokenAmount": "990654755978612", + "feeData": Object { + "metabridge": Object { + "amount": "8750000000000", + "asset": Object { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + }, + }, + "requestId": "197c402f-cb96-4096-9f8c-54aed84ca776", + "srcAsset": Object { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + "srcChainId": 42161, + "srcTokenAmount": "991250000000000", + "steps": Array [ + Object { + "action": "bridge", + "destAmount": "990654755978612", + "destAsset": Object { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 10, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.63", + "symbol": "ETH", + }, + "destChainId": 10, + "protocol": Object { + "displayName": "Across", + "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png", + "name": "across", + }, + "srcAmount": "991250000000000", + "srcAsset": Object { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + "srcChainId": 42161, + }, + ], + }, + "slippagePercentage": 0, + "startTime": 1729964825189, + "status": Object { + "srcChain": Object { + "chainId": 42161, + "txHash": "0xsrcTxHash1", + }, + "status": "PENDING", + }, + "targetContractAddress": "0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC", + "txMetaId": "bridgeTxMetaId1", + }, +} +`; From b85ed6776d507ef2c85fe8bc4a19fa4e22aeb33e Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:38:40 -0500 Subject: [PATCH 24/49] chore: fix lint error --- .../src/bridge-status-controller.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index 76d314fd5b..09c2a7f910 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable jest/no-restricted-matchers */ /* eslint-disable jest/no-conditional-in-test */ import { BridgeClientId } from '@metamask/bridge-controller'; import type { TransactionMeta } from '@metamask/transaction-controller'; From e2b6bf44a7774e0444cbc949d78e9b8ea10601cb Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 13 Feb 2025 17:42:59 -0500 Subject: [PATCH 25/49] chore: update CODEOWNERS --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 886355d6da..8ecf2033ef 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -39,6 +39,7 @@ ## Swaps-Bridge Team /packages/bridge-controller @MetaMask/swaps-engineers +/packages/bridge-status-controller @MetaMask/swaps-engineers ## Portfolio Team /packages/token-search-discovery-controller @MetaMask/portfolio @@ -124,3 +125,5 @@ /packages/bridge-controller/CHANGELOG.md @MetaMask/swaps-engineers @MetaMask/wallet-framework-engineers /packages/remote-feature-flag-controller/package.json @MetaMask/extension-platform @MetaMask/mobile-platform @MetaMask/wallet-framework-engineers /packages/remote-feature-flag-controller/CHANGELOG.md @MetaMask/extension-platform @MetaMask/mobile-platform @MetaMask/wallet-framework-engineers +/packages/bridge-status-controller/package.json @MetaMask/swaps-engineers @MetaMask/wallet-framework-engineers +/packages/bridge-status-controller/CHANGELOG.md @MetaMask/swaps-engineers @MetaMask/wallet-framework-engineers From bc586c02a8ddbdb672f7d657d92945bb5904ab2f Mon Sep 17 00:00:00 2001 From: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Sat, 15 Feb 2025 06:34:53 +0900 Subject: [PATCH 26/49] Update packages/bridge-status-controller/package.json Co-authored-by: Elliot Winkler --- packages/bridge-status-controller/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 3cf965af2b..b2d290e7fd 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -74,9 +74,9 @@ "typescript": "~5.2.2" }, "peerDependencies": { - "@metamask/accounts-controller": "^23.1.0", - "@metamask/network-controller": "^22.2.1", - "@metamask/transaction-controller": "^45.1.0" + "@metamask/accounts-controller": "^23.0.0", + "@metamask/network-controller": "^22.0.0", + "@metamask/transaction-controller": "^45.0.0" }, "engines": { "node": "^18.18 || >=20" From 490595bca19f7beee86052f5ecb2f5ee78b08d0b Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 14 Feb 2025 16:35:55 -0500 Subject: [PATCH 27/49] fix: move devDeps to deps for transaction controller --- .vscode/launch.json | 64 +++++++++++++++++ packages/bridge-controller/package.json | 4 +- .../bridge-status-controller/package.json | 2 +- yarn.lock | 71 +++++++++++++++++-- 4 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..bb8b8618b4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,64 @@ +{ + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Jest: current file", + "program": "${workspaceFolder}/node_modules/.bin/jest", + "args": [ + "${fileBasenameNoExtension}", + "--config", + "--runInBand", + "jest.config.js" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "windows": { + "program": "${workspaceFolder}/node_modules/jest/bin/jest" + } + }, + { + "type": "node", + "request": "launch", + "name": "Jest Integration: current file", + "program": "${workspaceFolder}/node_modules/.bin/jest", + "args": [ + "${fileBasenameNoExtension}", + "--config", + "jest.integration.config.js", + "--testTimeout", + "30000" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "windows": { + "program": "${workspaceFolder}/node_modules/jest/bin/jest" + } + }, + { + "type": "node", + "request": "launch", + "name": "Test E2E: current file", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "yarn", + "runtimeArgs": [ + "test:e2e:single", + "${relativeFile}", + "--browser=${input:browserToUse}", + "--debug" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } + ], + "inputs": [ + { + "type": "pickString", + "id": "browserToUse", + "description": "Which browser do you want to test with?", + "options": ["chrome", "firefox", "all"], + "default": "chrome" + } + ], + "version": "0.2.0" +} diff --git a/packages/bridge-controller/package.json b/packages/bridge-controller/package.json index 0706efea2b..63caf6186b 100644 --- a/packages/bridge-controller/package.json +++ b/packages/bridge-controller/package.json @@ -51,7 +51,8 @@ "@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/transaction-controller": "^45.1.0", + "@metamask/utils": "^11.1.0", "ethers": "^6.12.0" }, "devDependencies": { @@ -60,7 +61,6 @@ "@metamask/eth-json-rpc-provider": "^4.1.8", "@metamask/json-rpc-engine": "^10.0.3", "@metamask/network-controller": "^22.2.1", - "@metamask/transaction-controller": "^46.0.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index b2d290e7fd..e6be54cdf6 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -51,6 +51,7 @@ "@metamask/controller-utils": "^11.5.0", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/polling-controller": "^12.0.3", + "@metamask/transaction-controller": "^45.1.0", "@metamask/utils": "^11.1.0", "ethers": "^6.12.0" }, @@ -61,7 +62,6 @@ "@metamask/eth-json-rpc-provider": "^4.1.8", "@metamask/json-rpc-engine": "^10.0.3", "@metamask/network-controller": "^22.2.1", - "@metamask/transaction-controller": "^45.1.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", diff --git a/yarn.lock b/yarn.lock index c9cf67b076..9b1b0566f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2340,6 +2340,31 @@ __metadata: languageName: node linkType: hard +"@metamask/accounts-controller@npm:^23.1.0": + version: 23.1.0 + resolution: "@metamask/accounts-controller@npm:23.1.0" + dependencies: + "@ethereumjs/util": "npm:^8.1.0" + "@metamask/base-controller": "npm:^8.0.0" + "@metamask/eth-snap-keyring": "npm:^10.0.0" + "@metamask/keyring-api": "npm:^17.0.0" + "@metamask/keyring-internal-api": "npm:^4.0.1" + "@metamask/snaps-sdk": "npm:^6.17.1" + "@metamask/snaps-utils": "npm:^8.10.0" + "@metamask/utils": "npm:^11.1.0" + deepmerge: "npm:^4.2.2" + ethereum-cryptography: "npm:^2.1.2" + immer: "npm:^9.0.6" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/keyring-controller": ^19.0.0 + "@metamask/providers": ^18.1.0 + "@metamask/snaps-controllers": ^9.19.0 + webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0 + checksum: 10/f96be18434d1568ad8e8c9455c8d35a8924ffe8a59610833aec96db39d7df7e609ec1231533be973bbf63f23244bbfbc6a41c8b637aa0c3d36c391b43f6aba98 + languageName: node + linkType: hard + "@metamask/accounts-controller@npm:^24.0.0, @metamask/accounts-controller@workspace:packages/accounts-controller": version: 0.0.0-use.local resolution: "@metamask/accounts-controller@workspace:packages/accounts-controller" @@ -2595,8 +2620,8 @@ __metadata: "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/network-controller": "npm:^22.2.1" "@metamask/polling-controller": "npm:^12.0.3" - "@metamask/transaction-controller": "npm:^46.0.0" - "@metamask/utils": "npm:^11.2.0" + "@metamask/transaction-controller": "npm:^45.1.0" + "@metamask/utils": "npm:^11.1.0" "@types/jest": "npm:^27.4.1" deepmerge: "npm:^4.2.2" ethers: "npm:^6.12.0" @@ -2643,9 +2668,9 @@ __metadata: typedoc-plugin-missing-exports: "npm:^2.0.0" typescript: "npm:~5.2.2" peerDependencies: - "@metamask/accounts-controller": ^23.1.0 - "@metamask/network-controller": ^22.2.1 - "@metamask/transaction-controller": ^45.1.0 + "@metamask/accounts-controller": ^23.0.0 + "@metamask/network-controller": ^22.0.0 + "@metamask/transaction-controller": ^45.0.0 languageName: unknown linkType: soft @@ -3382,7 +3407,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/keyring-internal-api@npm:^4.0.2": +"@metamask/keyring-internal-api@npm:^4.0.1, @metamask/keyring-internal-api@npm:^4.0.2": version: 4.0.2 resolution: "@metamask/keyring-internal-api@npm:4.0.2" dependencies: @@ -4214,6 +4239,40 @@ __metadata: languageName: unknown linkType: soft +"@metamask/transaction-controller@npm:^45.1.0": + version: 45.1.0 + resolution: "@metamask/transaction-controller@npm:45.1.0" + dependencies: + "@ethereumjs/common": "npm:^4.4.0" + "@ethereumjs/tx": "npm:^5.4.0" + "@ethereumjs/util": "npm:^8.1.0" + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/providers": "npm:^5.7.0" + "@metamask/base-controller": "npm:^8.0.0" + "@metamask/controller-utils": "npm:^11.5.0" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/metamask-eth-abis": "npm:^3.1.1" + "@metamask/nonce-tracker": "npm:^6.0.0" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/utils": "npm:^11.1.0" + async-mutex: "npm:^0.5.0" + bn.js: "npm:^5.2.1" + eth-method-registry: "npm:^4.0.0" + fast-json-patch: "npm:^3.1.1" + lodash: "npm:^4.17.21" + uuid: "npm:^8.3.2" + peerDependencies: + "@babel/runtime": ^7.0.0 + "@metamask/accounts-controller": ^23.0.0 + "@metamask/approval-controller": ^7.0.0 + "@metamask/eth-block-tracker": ">=9" + "@metamask/gas-fee-controller": ^22.0.0 + "@metamask/network-controller": ^22.0.0 + checksum: 10/4be40e2315e512db873c436f87cfb43f63623491c98cbb22c28c24091cf666c2428e9916f2ba1706743504ba8af505dc2c3fa5549c1ed99f50868284eb5fe07f + languageName: node + linkType: hard + "@metamask/transaction-controller@npm:^46.0.0, @metamask/transaction-controller@workspace:packages/transaction-controller": version: 0.0.0-use.local resolution: "@metamask/transaction-controller@workspace:packages/transaction-controller" From 6c8d1183ff4bbf0de031a0da8f9eaac345945404 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 14 Feb 2025 16:36:16 -0500 Subject: [PATCH 28/49] Revert "fix: move devDeps to deps for transaction controller" This reverts commit 810a14198ae5f3baa3de34cf4ec57b883dc7b986. --- .vscode/launch.json | 64 ------------------- packages/bridge-controller/package.json | 2 +- .../bridge-status-controller/package.json | 2 +- 3 files changed, 2 insertions(+), 66 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index bb8b8618b4..0000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Jest: current file", - "program": "${workspaceFolder}/node_modules/.bin/jest", - "args": [ - "${fileBasenameNoExtension}", - "--config", - "--runInBand", - "jest.config.js" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "windows": { - "program": "${workspaceFolder}/node_modules/jest/bin/jest" - } - }, - { - "type": "node", - "request": "launch", - "name": "Jest Integration: current file", - "program": "${workspaceFolder}/node_modules/.bin/jest", - "args": [ - "${fileBasenameNoExtension}", - "--config", - "jest.integration.config.js", - "--testTimeout", - "30000" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "windows": { - "program": "${workspaceFolder}/node_modules/jest/bin/jest" - } - }, - { - "type": "node", - "request": "launch", - "name": "Test E2E: current file", - "cwd": "${workspaceFolder}", - "runtimeExecutable": "yarn", - "runtimeArgs": [ - "test:e2e:single", - "${relativeFile}", - "--browser=${input:browserToUse}", - "--debug" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - } - ], - "inputs": [ - { - "type": "pickString", - "id": "browserToUse", - "description": "Which browser do you want to test with?", - "options": ["chrome", "firefox", "all"], - "default": "chrome" - } - ], - "version": "0.2.0" -} diff --git a/packages/bridge-controller/package.json b/packages/bridge-controller/package.json index 63caf6186b..8e097e5063 100644 --- a/packages/bridge-controller/package.json +++ b/packages/bridge-controller/package.json @@ -51,7 +51,6 @@ "@metamask/controller-utils": "^11.5.0", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/polling-controller": "^12.0.3", - "@metamask/transaction-controller": "^45.1.0", "@metamask/utils": "^11.1.0", "ethers": "^6.12.0" }, @@ -61,6 +60,7 @@ "@metamask/eth-json-rpc-provider": "^4.1.8", "@metamask/json-rpc-engine": "^10.0.3", "@metamask/network-controller": "^22.2.1", + "@metamask/transaction-controller": "^45.1.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index e6be54cdf6..b2d290e7fd 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -51,7 +51,6 @@ "@metamask/controller-utils": "^11.5.0", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/polling-controller": "^12.0.3", - "@metamask/transaction-controller": "^45.1.0", "@metamask/utils": "^11.1.0", "ethers": "^6.12.0" }, @@ -62,6 +61,7 @@ "@metamask/eth-json-rpc-provider": "^4.1.8", "@metamask/json-rpc-engine": "^10.0.3", "@metamask/network-controller": "^22.2.1", + "@metamask/transaction-controller": "^45.1.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", From 81f3699eb2c0255dfc2e79e960caab8d96a0637b Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 14 Feb 2025 16:37:10 -0500 Subject: [PATCH 29/49] fix: move transaction controller devDeps to deps --- packages/bridge-controller/package.json | 2 +- packages/bridge-status-controller/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bridge-controller/package.json b/packages/bridge-controller/package.json index 8e097e5063..63caf6186b 100644 --- a/packages/bridge-controller/package.json +++ b/packages/bridge-controller/package.json @@ -51,6 +51,7 @@ "@metamask/controller-utils": "^11.5.0", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/polling-controller": "^12.0.3", + "@metamask/transaction-controller": "^45.1.0", "@metamask/utils": "^11.1.0", "ethers": "^6.12.0" }, @@ -60,7 +61,6 @@ "@metamask/eth-json-rpc-provider": "^4.1.8", "@metamask/json-rpc-engine": "^10.0.3", "@metamask/network-controller": "^22.2.1", - "@metamask/transaction-controller": "^45.1.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index b2d290e7fd..e6be54cdf6 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -51,6 +51,7 @@ "@metamask/controller-utils": "^11.5.0", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/polling-controller": "^12.0.3", + "@metamask/transaction-controller": "^45.1.0", "@metamask/utils": "^11.1.0", "ethers": "^6.12.0" }, @@ -61,7 +62,6 @@ "@metamask/eth-json-rpc-provider": "^4.1.8", "@metamask/json-rpc-engine": "^10.0.3", "@metamask/network-controller": "^22.2.1", - "@metamask/transaction-controller": "^45.1.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", From b8479506a018f6f42b536a8dbf1cac2d17981e40 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 14 Feb 2025 16:42:33 -0500 Subject: [PATCH 30/49] chore: bump tx controller version --- packages/bridge-controller/package.json | 2 +- packages/bridge-status-controller/package.json | 4 ++-- yarn.lock | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/bridge-controller/package.json b/packages/bridge-controller/package.json index 63caf6186b..5af056880f 100644 --- a/packages/bridge-controller/package.json +++ b/packages/bridge-controller/package.json @@ -51,7 +51,7 @@ "@metamask/controller-utils": "^11.5.0", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/polling-controller": "^12.0.3", - "@metamask/transaction-controller": "^45.1.0", + "@metamask/transaction-controller": "^46.0.0", "@metamask/utils": "^11.1.0", "ethers": "^6.12.0" }, diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index e6be54cdf6..021b7684ec 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -51,7 +51,7 @@ "@metamask/controller-utils": "^11.5.0", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/polling-controller": "^12.0.3", - "@metamask/transaction-controller": "^45.1.0", + "@metamask/transaction-controller": "^46.0.0", "@metamask/utils": "^11.1.0", "ethers": "^6.12.0" }, @@ -76,7 +76,7 @@ "peerDependencies": { "@metamask/accounts-controller": "^23.0.0", "@metamask/network-controller": "^22.0.0", - "@metamask/transaction-controller": "^45.0.0" + "@metamask/transaction-controller": "^46.0.0" }, "engines": { "node": "^18.18 || >=20" diff --git a/yarn.lock b/yarn.lock index 9b1b0566f0..7c4d7ac5a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2654,7 +2654,7 @@ __metadata: "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/network-controller": "npm:^22.2.1" "@metamask/polling-controller": "npm:^12.0.3" - "@metamask/transaction-controller": "npm:^45.1.0" + "@metamask/transaction-controller": "npm:^46.0.0" "@metamask/utils": "npm:^11.1.0" "@types/jest": "npm:^27.4.1" deepmerge: "npm:^4.2.2" @@ -2670,7 +2670,7 @@ __metadata: peerDependencies: "@metamask/accounts-controller": ^23.0.0 "@metamask/network-controller": ^22.0.0 - "@metamask/transaction-controller": ^45.0.0 + "@metamask/transaction-controller": ^46.0.0 languageName: unknown linkType: soft From ce70967edbb394976effabbcc059adfeff779b94 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 14 Feb 2025 16:49:51 -0500 Subject: [PATCH 31/49] chore: move utils into own folder --- .../src/bridge-status-controller.test.ts | 2 +- .../src/bridge-status-controller.ts | 5 ++++- .../src/{utils.test.ts => utils/bridge-status.test.ts} | 4 ++-- .../src/{utils.ts => utils/bridge-status.ts} | 4 ++-- .../src/{ => utils}/validators.test.ts | 2 +- .../bridge-status-controller/src/{ => utils}/validators.ts | 6 +++--- 6 files changed, 13 insertions(+), 10 deletions(-) rename packages/bridge-status-controller/src/{utils.test.ts => utils/bridge-status.test.ts} (99%) rename packages/bridge-status-controller/src/{utils.ts => utils/bridge-status.ts} (99%) rename packages/bridge-status-controller/src/{ => utils}/validators.test.ts (99%) rename packages/bridge-status-controller/src/{ => utils}/validators.ts (97%) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index 09c2a7f910..665461f744 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -14,7 +14,7 @@ import type { StartPollingForBridgeTxStatusArgsSerialized, BridgeHistoryItem, } from './types'; -import * as bridgeStatusUtils from './utils'; +import * as bridgeStatusUtils from './utils/bridge-status'; import { flushPromises } from '../../../tests/helpers'; const EMPTY_INIT_STATE = { diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 353e98384e..a301d0effb 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -15,7 +15,10 @@ import type { BridgeStatusState, FetchFunction, } from './types'; -import { fetchBridgeTxStatus, getStatusRequestWithSrcTxHash } from './utils'; +import { + fetchBridgeTxStatus, + getStatusRequestWithSrcTxHash, +} from './utils/bridge-status'; const metadata: StateMetadata = { // We want to persist the bridge status state so that we can show the proper data for the Activity list diff --git a/packages/bridge-status-controller/src/utils.test.ts b/packages/bridge-status-controller/src/utils/bridge-status.test.ts similarity index 99% rename from packages/bridge-status-controller/src/utils.test.ts rename to packages/bridge-status-controller/src/utils/bridge-status.test.ts index a4032c903e..bbf334a9f6 100644 --- a/packages/bridge-status-controller/src/utils.test.ts +++ b/packages/bridge-status-controller/src/utils/bridge-status.test.ts @@ -1,11 +1,11 @@ import { BridgeClientId, FeeType } from '@metamask/bridge-controller'; -import type { StatusRequestWithSrcTxHash, FetchFunction } from './types'; import { fetchBridgeTxStatus, BRIDGE_STATUS_BASE_URL, getStatusRequestDto, -} from './utils'; +} from './bridge-status'; +import type { StatusRequestWithSrcTxHash, FetchFunction } from '../types'; describe('utils', () => { const mockStatusRequest: StatusRequestWithSrcTxHash = { diff --git a/packages/bridge-status-controller/src/utils.ts b/packages/bridge-status-controller/src/utils/bridge-status.ts similarity index 99% rename from packages/bridge-status-controller/src/utils.ts rename to packages/bridge-status-controller/src/utils/bridge-status.ts index 0b8c0a7e9a..8a8fa50936 100644 --- a/packages/bridge-status-controller/src/utils.ts +++ b/packages/bridge-status-controller/src/utils/bridge-status.ts @@ -1,13 +1,13 @@ import type { Quote } from '@metamask/bridge-controller'; import { getBridgeApiBaseUrl } from '@metamask/bridge-controller'; +import { validateResponse, validators } from './validators'; import type { StatusResponse, StatusRequestWithSrcTxHash, StatusRequestDto, FetchFunction, -} from './types'; -import { validateResponse, validators } from './validators'; +} from '../types'; export const getClientIdHeader = (clientId: string) => ({ 'X-Client-Id': clientId, diff --git a/packages/bridge-status-controller/src/validators.test.ts b/packages/bridge-status-controller/src/utils/validators.test.ts similarity index 99% rename from packages/bridge-status-controller/src/validators.test.ts rename to packages/bridge-status-controller/src/utils/validators.test.ts index 00c6cd7c93..90128f6583 100644 --- a/packages/bridge-status-controller/src/validators.test.ts +++ b/packages/bridge-status-controller/src/utils/validators.test.ts @@ -1,5 +1,5 @@ -import type { StatusResponse } from './types'; import { validateResponse, validators } from './validators'; +import type { StatusResponse } from '../types'; const BridgeTxStatusResponses = { STATUS_PENDING_VALID: { diff --git a/packages/bridge-status-controller/src/validators.ts b/packages/bridge-status-controller/src/utils/validators.ts similarity index 97% rename from packages/bridge-status-controller/src/validators.ts rename to packages/bridge-status-controller/src/utils/validators.ts index 21697baf21..cc32e0f031 100644 --- a/packages/bridge-status-controller/src/validators.ts +++ b/packages/bridge-status-controller/src/utils/validators.ts @@ -1,8 +1,8 @@ import { isValidHexAddress } from '@metamask/controller-utils'; -import type { DestChainStatus, SrcChainStatus, Asset } from './types'; -import { BridgeId, StatusTypes } from './types'; -import { BRIDGE_STATUS_BASE_URL } from './utils'; +import { BRIDGE_STATUS_BASE_URL } from './bridge-status'; +import type { DestChainStatus, SrcChainStatus, Asset } from '../types'; +import { BridgeId, StatusTypes } from '../types'; type Validator = { property: keyof ExpectedResponse | string; From c3e61a5276cd88762981f2348ba2d5174475b529 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:03:33 -0500 Subject: [PATCH 32/49] chore: add index with export --- .../src/bridge-status-controller.ts | 2 +- .../bridge-status-controller/src/index.ts | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 packages/bridge-status-controller/src/index.ts diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index a301d0effb..8bc4a38d4c 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -36,7 +36,7 @@ type SrcTxMetaId = string; export type FetchBridgeTxStatusArgs = { bridgeTxMetaId: string; }; -export default class BridgeStatusController extends StaticIntervalPollingController()< +export class BridgeStatusController extends StaticIntervalPollingController()< typeof BRIDGE_STATUS_CONTROLLER_NAME, BridgeStatusControllerState, BridgeStatusControllerMessenger diff --git a/packages/bridge-status-controller/src/index.ts b/packages/bridge-status-controller/src/index.ts new file mode 100644 index 0000000000..88a062157f --- /dev/null +++ b/packages/bridge-status-controller/src/index.ts @@ -0,0 +1,37 @@ +// Export constants +export { + REFRESH_INTERVAL_MS, + DEFAULT_BRIDGE_STATUS_STATE, + DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE, +} from './constants'; + +export type { + FetchFunction, + StatusRequest, + StatusRequestDto, + StatusRequestWithSrcTxHash, + Asset, + SrcChainStatus, + DestChainStatus, + StatusResponse, + RefuelStatusResponse, + RefuelData, + BridgeHistoryItem, + BridgeStatusState, + BridgeStatusControllerState, + BridgeStatusControllerMessenger, + StartPollingForBridgeTxStatusArgs, + StartPollingForBridgeTxStatusArgsSerialized, + TokenAmountValuesSerialized, + QuoteMetadataSerialized, +} from './types'; + +export { + StatusTypes, + BridgeId, + FeeType, + ActionTypes, + BridgeStatusAction, +} from './types'; + +export { BridgeStatusController } from './bridge-status-controller'; From 052b4254afab8eac1d569ca049c0f470ca2fb95f Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:03:55 -0500 Subject: [PATCH 33/49] fix: broken import --- packages/bridge-status-controller/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-status-controller/src/types.ts b/packages/bridge-status-controller/src/types.ts index 5fd61516c0..de34a520f3 100644 --- a/packages/bridge-status-controller/src/types.ts +++ b/packages/bridge-status-controller/src/types.ts @@ -18,7 +18,7 @@ import type { import type { TransactionControllerGetStateAction } from '@metamask/transaction-controller'; import type { TransactionMeta } from '@metamask/transaction-controller'; -import type BridgeStatusController from './bridge-status-controller'; +import type { BridgeStatusController } from './bridge-status-controller'; import type { BRIDGE_STATUS_CONTROLLER_NAME } from './constants'; // All fields need to be types not interfaces, same with their children fields From 6882491d8fc940af7883d96284b9226222ac030e Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:04:47 -0500 Subject: [PATCH 34/49] chore: align package version --- .../bridge-status-controller/package.json | 4 +-- yarn.lock | 29 ++----------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 021b7684ec..676b8384b5 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -56,7 +56,7 @@ "ethers": "^6.12.0" }, "devDependencies": { - "@metamask/accounts-controller": "^23.1.0", + "@metamask/accounts-controller": "^24.0.0", "@metamask/auto-changelog": "^3.4.4", "@metamask/bridge-controller": "^0.0.0", "@metamask/eth-json-rpc-provider": "^4.1.8", @@ -74,7 +74,7 @@ "typescript": "~5.2.2" }, "peerDependencies": { - "@metamask/accounts-controller": "^23.0.0", + "@metamask/accounts-controller": "^24.0.0", "@metamask/network-controller": "^22.0.0", "@metamask/transaction-controller": "^46.0.0" }, diff --git a/yarn.lock b/yarn.lock index 7c4d7ac5a7..827cf96236 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2340,31 +2340,6 @@ __metadata: languageName: node linkType: hard -"@metamask/accounts-controller@npm:^23.1.0": - version: 23.1.0 - resolution: "@metamask/accounts-controller@npm:23.1.0" - dependencies: - "@ethereumjs/util": "npm:^8.1.0" - "@metamask/base-controller": "npm:^8.0.0" - "@metamask/eth-snap-keyring": "npm:^10.0.0" - "@metamask/keyring-api": "npm:^17.0.0" - "@metamask/keyring-internal-api": "npm:^4.0.1" - "@metamask/snaps-sdk": "npm:^6.17.1" - "@metamask/snaps-utils": "npm:^8.10.0" - "@metamask/utils": "npm:^11.1.0" - deepmerge: "npm:^4.2.2" - ethereum-cryptography: "npm:^2.1.2" - immer: "npm:^9.0.6" - uuid: "npm:^8.3.2" - peerDependencies: - "@metamask/keyring-controller": ^19.0.0 - "@metamask/providers": ^18.1.0 - "@metamask/snaps-controllers": ^9.19.0 - webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0 - checksum: 10/f96be18434d1568ad8e8c9455c8d35a8924ffe8a59610833aec96db39d7df7e609ec1231533be973bbf63f23244bbfbc6a41c8b637aa0c3d36c391b43f6aba98 - languageName: node - linkType: hard - "@metamask/accounts-controller@npm:^24.0.0, @metamask/accounts-controller@workspace:packages/accounts-controller": version: 0.0.0-use.local resolution: "@metamask/accounts-controller@workspace:packages/accounts-controller" @@ -2644,7 +2619,7 @@ __metadata: version: 0.0.0-use.local resolution: "@metamask/bridge-status-controller@workspace:packages/bridge-status-controller" dependencies: - "@metamask/accounts-controller": "npm:^23.1.0" + "@metamask/accounts-controller": "npm:^24.0.0" "@metamask/auto-changelog": "npm:^3.4.4" "@metamask/base-controller": "npm:^8.0.0" "@metamask/bridge-controller": "npm:^0.0.0" @@ -2668,7 +2643,7 @@ __metadata: typedoc-plugin-missing-exports: "npm:^2.0.0" typescript: "npm:~5.2.2" peerDependencies: - "@metamask/accounts-controller": ^23.0.0 + "@metamask/accounts-controller": ^24.0.0 "@metamask/network-controller": ^22.0.0 "@metamask/transaction-controller": ^46.0.0 languageName: unknown From e1a6ff2e7177a0e3b0ee6e09dae99d4f686e6f6c Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:10:44 -0500 Subject: [PATCH 35/49] fix: broken import --- .../src/bridge-status-controller.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index 665461f744..b79647acaf 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -4,7 +4,7 @@ import { BridgeClientId } from '@metamask/bridge-controller'; import type { TransactionMeta } from '@metamask/transaction-controller'; import { numberToHex } from '@metamask/utils'; -import BridgeStatusController from './bridge-status-controller'; +import { BridgeStatusController } from './bridge-status-controller'; import { DEFAULT_BRIDGE_STATUS_STATE } from './constants'; import type { BridgeStatusControllerMessenger } from './types'; import type { From b8a259279c433da4f827174a38992c828b078b16 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:30:55 -0500 Subject: [PATCH 36/49] chore: update yarn lock --- yarn.lock | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/yarn.lock b/yarn.lock index 827cf96236..a6db26a4ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2595,7 +2595,7 @@ __metadata: "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/network-controller": "npm:^22.2.1" "@metamask/polling-controller": "npm:^12.0.3" - "@metamask/transaction-controller": "npm:^45.1.0" + "@metamask/transaction-controller": "npm:^46.0.0" "@metamask/utils": "npm:^11.1.0" "@types/jest": "npm:^27.4.1" deepmerge: "npm:^4.2.2" @@ -3382,7 +3382,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/keyring-internal-api@npm:^4.0.1, @metamask/keyring-internal-api@npm:^4.0.2": +"@metamask/keyring-internal-api@npm:^4.0.2": version: 4.0.2 resolution: "@metamask/keyring-internal-api@npm:4.0.2" dependencies: @@ -4214,40 +4214,6 @@ __metadata: languageName: unknown linkType: soft -"@metamask/transaction-controller@npm:^45.1.0": - version: 45.1.0 - resolution: "@metamask/transaction-controller@npm:45.1.0" - dependencies: - "@ethereumjs/common": "npm:^4.4.0" - "@ethereumjs/tx": "npm:^5.4.0" - "@ethereumjs/util": "npm:^8.1.0" - "@ethersproject/abi": "npm:^5.7.0" - "@ethersproject/contracts": "npm:^5.7.0" - "@ethersproject/providers": "npm:^5.7.0" - "@metamask/base-controller": "npm:^8.0.0" - "@metamask/controller-utils": "npm:^11.5.0" - "@metamask/eth-query": "npm:^4.0.0" - "@metamask/metamask-eth-abis": "npm:^3.1.1" - "@metamask/nonce-tracker": "npm:^6.0.0" - "@metamask/rpc-errors": "npm:^7.0.2" - "@metamask/utils": "npm:^11.1.0" - async-mutex: "npm:^0.5.0" - bn.js: "npm:^5.2.1" - eth-method-registry: "npm:^4.0.0" - fast-json-patch: "npm:^3.1.1" - lodash: "npm:^4.17.21" - uuid: "npm:^8.3.2" - peerDependencies: - "@babel/runtime": ^7.0.0 - "@metamask/accounts-controller": ^23.0.0 - "@metamask/approval-controller": ^7.0.0 - "@metamask/eth-block-tracker": ">=9" - "@metamask/gas-fee-controller": ^22.0.0 - "@metamask/network-controller": ^22.0.0 - checksum: 10/4be40e2315e512db873c436f87cfb43f63623491c98cbb22c28c24091cf666c2428e9916f2ba1706743504ba8af505dc2c3fa5549c1ed99f50868284eb5fe07f - languageName: node - linkType: hard - "@metamask/transaction-controller@npm:^46.0.0, @metamask/transaction-controller@workspace:packages/transaction-controller": version: 0.0.0-use.local resolution: "@metamask/transaction-controller@workspace:packages/transaction-controller" From 7cf0f59a54d4b4b94fbc6bc085ed2bf37881fe02 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 13:40:33 -0500 Subject: [PATCH 37/49] chore: bump metamask/utils version to align with rest of codebase --- packages/bridge-controller/package.json | 2 +- packages/bridge-status-controller/package.json | 2 +- yarn.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bridge-controller/package.json b/packages/bridge-controller/package.json index 5af056880f..9ed8b27921 100644 --- a/packages/bridge-controller/package.json +++ b/packages/bridge-controller/package.json @@ -52,7 +52,7 @@ "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/polling-controller": "^12.0.3", "@metamask/transaction-controller": "^46.0.0", - "@metamask/utils": "^11.1.0", + "@metamask/utils": "^11.2.0", "ethers": "^6.12.0" }, "devDependencies": { diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 676b8384b5..1e6b2ac198 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -52,7 +52,7 @@ "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/polling-controller": "^12.0.3", "@metamask/transaction-controller": "^46.0.0", - "@metamask/utils": "^11.1.0", + "@metamask/utils": "^11.2.0", "ethers": "^6.12.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index a6db26a4ee..f9c2d845f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2596,7 +2596,7 @@ __metadata: "@metamask/network-controller": "npm:^22.2.1" "@metamask/polling-controller": "npm:^12.0.3" "@metamask/transaction-controller": "npm:^46.0.0" - "@metamask/utils": "npm:^11.1.0" + "@metamask/utils": "npm:^11.2.0" "@types/jest": "npm:^27.4.1" deepmerge: "npm:^4.2.2" ethers: "npm:^6.12.0" @@ -2630,7 +2630,7 @@ __metadata: "@metamask/network-controller": "npm:^22.2.1" "@metamask/polling-controller": "npm:^12.0.3" "@metamask/transaction-controller": "npm:^46.0.0" - "@metamask/utils": "npm:^11.1.0" + "@metamask/utils": "npm:^11.2.0" "@types/jest": "npm:^27.4.1" deepmerge: "npm:^4.2.2" ethers: "npm:^6.12.0" From df821b28721a30e88a98d8512271495270487452 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:18:44 -0500 Subject: [PATCH 38/49] chore: remove unused dep --- packages/bridge-status-controller/package.json | 3 +-- yarn.lock | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 1e6b2ac198..1b6379cf68 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -52,8 +52,7 @@ "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/polling-controller": "^12.0.3", "@metamask/transaction-controller": "^46.0.0", - "@metamask/utils": "^11.2.0", - "ethers": "^6.12.0" + "@metamask/utils": "^11.2.0" }, "devDependencies": { "@metamask/accounts-controller": "^24.0.0", diff --git a/yarn.lock b/yarn.lock index f9c2d845f6..cb4aa342cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2633,7 +2633,6 @@ __metadata: "@metamask/utils": "npm:^11.2.0" "@types/jest": "npm:^27.4.1" deepmerge: "npm:^4.2.2" - ethers: "npm:^6.12.0" jest: "npm:^27.5.1" jest-environment-jsdom: "npm:^27.5.1" lodash: "npm:^4.17.21" From 45fb6c82f7f445873de3a7e6a2da6dcc34217784 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:21:05 -0500 Subject: [PATCH 39/49] chore: remove unneeded @metamask/json-rpc-engine dependency --- packages/bridge-controller/package.json | 1 - packages/bridge-status-controller/package.json | 2 -- yarn.lock | 3 --- 3 files changed, 6 deletions(-) diff --git a/packages/bridge-controller/package.json b/packages/bridge-controller/package.json index 9ed8b27921..95ad01ece6 100644 --- a/packages/bridge-controller/package.json +++ b/packages/bridge-controller/package.json @@ -59,7 +59,6 @@ "@metamask/accounts-controller": "^24.0.0", "@metamask/auto-changelog": "^3.4.4", "@metamask/eth-json-rpc-provider": "^4.1.8", - "@metamask/json-rpc-engine": "^10.0.3", "@metamask/network-controller": "^22.2.1", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 1b6379cf68..a9a8933fac 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -58,8 +58,6 @@ "@metamask/accounts-controller": "^24.0.0", "@metamask/auto-changelog": "^3.4.4", "@metamask/bridge-controller": "^0.0.0", - "@metamask/eth-json-rpc-provider": "^4.1.8", - "@metamask/json-rpc-engine": "^10.0.3", "@metamask/network-controller": "^22.2.1", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", diff --git a/yarn.lock b/yarn.lock index cb4aa342cf..beb36da49f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2591,7 +2591,6 @@ __metadata: "@metamask/base-controller": "npm:^8.0.0" "@metamask/controller-utils": "npm:^11.5.0" "@metamask/eth-json-rpc-provider": "npm:^4.1.8" - "@metamask/json-rpc-engine": "npm:^10.0.3" "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/network-controller": "npm:^22.2.1" "@metamask/polling-controller": "npm:^12.0.3" @@ -2624,8 +2623,6 @@ __metadata: "@metamask/base-controller": "npm:^8.0.0" "@metamask/bridge-controller": "npm:^0.0.0" "@metamask/controller-utils": "npm:^11.5.0" - "@metamask/eth-json-rpc-provider": "npm:^4.1.8" - "@metamask/json-rpc-engine": "npm:^10.0.3" "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/network-controller": "npm:^22.2.1" "@metamask/polling-controller": "npm:^12.0.3" From fbe37fe63abcffa6f12452a197966abd2dfe53cb Mon Sep 17 00:00:00 2001 From: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 19 Feb 2025 09:21:39 +0900 Subject: [PATCH 40/49] Update packages/bridge-status-controller/tsconfig.build.json Co-authored-by: Elliot Winkler --- packages/bridge-status-controller/tsconfig.build.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/bridge-status-controller/tsconfig.build.json b/packages/bridge-status-controller/tsconfig.build.json index 2b7b7f52b5..817b522d1e 100644 --- a/packages/bridge-status-controller/tsconfig.build.json +++ b/packages/bridge-status-controller/tsconfig.build.json @@ -11,7 +11,8 @@ { "path": "../bridge-controller/tsconfig.build.json" }, { "path": "../controller-utils/tsconfig.build.json" }, { "path": "../network-controller/tsconfig.build.json" }, - { "path": "../polling-controller/tsconfig.build.json" } + { "path": "../polling-controller/tsconfig.build.json" }, + { "path": "../transaction-controller/tsconfig.build.json" } ], "include": ["../../types", "./src"] } From f407152fa83f843bd4abab378d1d485fc19c3870 Mon Sep 17 00:00:00 2001 From: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 19 Feb 2025 09:22:18 +0900 Subject: [PATCH 41/49] Update packages/bridge-status-controller/tsconfig.json Co-authored-by: Elliot Winkler --- packages/bridge-status-controller/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-status-controller/tsconfig.json b/packages/bridge-status-controller/tsconfig.json index 49cc85ffa3..97995227d3 100644 --- a/packages/bridge-status-controller/tsconfig.json +++ b/packages/bridge-status-controller/tsconfig.json @@ -9,8 +9,8 @@ { "path": "../base-controller" }, { "path": "../bridge-controller" }, { "path": "../controller-utils" }, - { "path": "../polling-controller" }, { "path": "../network-controller" }, + { "path": "../polling-controller" }, { "path": "../transaction-controller" } ], "include": ["../../types", "./src"] From 3e761e44626d5014fe985ab434d31e68ca59dd9f Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:23:07 -0500 Subject: [PATCH 42/49] chore: remove unneeded @metamask/metamask-eth-abis dependency --- packages/bridge-status-controller/package.json | 1 - yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index a9a8933fac..9b62e19104 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -49,7 +49,6 @@ "dependencies": { "@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/transaction-controller": "^46.0.0", "@metamask/utils": "^11.2.0" diff --git a/yarn.lock b/yarn.lock index beb36da49f..7799d6564d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2623,7 +2623,6 @@ __metadata: "@metamask/base-controller": "npm:^8.0.0" "@metamask/bridge-controller": "npm:^0.0.0" "@metamask/controller-utils": "npm:^11.5.0" - "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/network-controller": "npm:^22.2.1" "@metamask/polling-controller": "npm:^12.0.3" "@metamask/transaction-controller": "npm:^46.0.0" From 1817aab2babc1ca6735be2bfb235eb37dd0b8441 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:24:48 -0500 Subject: [PATCH 43/49] chore: move transaction controller devDeps --- packages/bridge-status-controller/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 9b62e19104..6f9f696baf 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -50,7 +50,6 @@ "@metamask/base-controller": "^8.0.0", "@metamask/controller-utils": "^11.5.0", "@metamask/polling-controller": "^12.0.3", - "@metamask/transaction-controller": "^46.0.0", "@metamask/utils": "^11.2.0" }, "devDependencies": { @@ -58,6 +57,7 @@ "@metamask/auto-changelog": "^3.4.4", "@metamask/bridge-controller": "^0.0.0", "@metamask/network-controller": "^22.2.1", + "@metamask/transaction-controller": "^46.0.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", From 0b09fa934d0fb90f1b38ae5123e64a5cf21bf934 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:25:41 -0500 Subject: [PATCH 44/49] chore: update dependency graph --- README.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 06b90d078f..d4c91f573e 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ linkStyle default opacity:0.5 approval_controller(["@metamask/approval-controller"]); assets_controllers(["@metamask/assets-controllers"]); base_controller(["@metamask/base-controller"]); + bridge_controller(["@metamask/bridge-controller"]); + bridge_status_controller(["@metamask/bridge-status-controller"]); build_utils(["@metamask/build-utils"]); composable_controller(["@metamask/composable-controller"]); controller_utils(["@metamask/controller-utils"]); @@ -108,8 +110,8 @@ linkStyle default opacity:0.5 transaction_controller(["@metamask/transaction-controller"]); user_operation_controller(["@metamask/user-operation-controller"]); accounts_controller --> base_controller; - accounts_controller --> keyring_controller; accounts_controller --> network_controller; + accounts_controller --> keyring_controller; address_book_controller --> base_controller; address_book_controller --> controller_utils; announcement_controller --> base_controller; @@ -124,6 +126,20 @@ linkStyle default opacity:0.5 assets_controllers --> permission_controller; assets_controllers --> preferences_controller; base_controller --> json_rpc_engine; + bridge_controller --> base_controller; + bridge_controller --> controller_utils; + bridge_controller --> polling_controller; + bridge_controller --> transaction_controller; + bridge_controller --> accounts_controller; + bridge_controller --> eth_json_rpc_provider; + bridge_controller --> network_controller; + bridge_status_controller --> base_controller; + bridge_status_controller --> controller_utils; + bridge_status_controller --> polling_controller; + bridge_status_controller --> accounts_controller; + bridge_status_controller --> bridge_controller; + bridge_status_controller --> network_controller; + bridge_status_controller --> transaction_controller; composable_controller --> base_controller; composable_controller --> json_rpc_engine; earn_controller --> base_controller; @@ -140,7 +156,6 @@ linkStyle default opacity:0.5 gas_fee_controller --> network_controller; json_rpc_middleware_stream --> json_rpc_engine; keyring_controller --> base_controller; - keyring_controller --> message_manager; logging_controller --> base_controller; logging_controller --> controller_utils; message_manager --> base_controller; @@ -151,6 +166,7 @@ linkStyle default opacity:0.5 multichain --> permission_controller; multichain_network_controller --> base_controller; multichain_network_controller --> keyring_controller; + multichain_network_controller --> network_controller; multichain_transactions_controller --> base_controller; multichain_transactions_controller --> polling_controller; multichain_transactions_controller --> accounts_controller; @@ -204,6 +220,7 @@ linkStyle default opacity:0.5 token_search_discovery_controller --> base_controller; transaction_controller --> base_controller; transaction_controller --> controller_utils; + transaction_controller --> remote_feature_flag_controller; transaction_controller --> accounts_controller; transaction_controller --> approval_controller; transaction_controller --> eth_json_rpc_provider; From d35a6191601e4f346994f21f00f53c9039a9bda7 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:28:51 -0500 Subject: [PATCH 45/49] chore: move transaction controller to devDeps for bridge controller --- packages/bridge-controller/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-controller/package.json b/packages/bridge-controller/package.json index 95ad01ece6..d6895095a0 100644 --- a/packages/bridge-controller/package.json +++ b/packages/bridge-controller/package.json @@ -51,7 +51,6 @@ "@metamask/controller-utils": "^11.5.0", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/polling-controller": "^12.0.3", - "@metamask/transaction-controller": "^46.0.0", "@metamask/utils": "^11.2.0", "ethers": "^6.12.0" }, @@ -60,6 +59,7 @@ "@metamask/auto-changelog": "^3.4.4", "@metamask/eth-json-rpc-provider": "^4.1.8", "@metamask/network-controller": "^22.2.1", + "@metamask/transaction-controller": "^46.0.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", From c440941882b0cba27f548f4e624c43cd283b6421 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:55:30 -0500 Subject: [PATCH 46/49] chore: add bridge-controller as a direct dependency of bridge-status-controller --- packages/bridge-status-controller/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 6f9f696baf..03747d635b 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -48,6 +48,7 @@ }, "dependencies": { "@metamask/base-controller": "^8.0.0", + "@metamask/bridge-controller": "^0.0.0", "@metamask/controller-utils": "^11.5.0", "@metamask/polling-controller": "^12.0.3", "@metamask/utils": "^11.2.0" @@ -55,7 +56,6 @@ "devDependencies": { "@metamask/accounts-controller": "^24.0.0", "@metamask/auto-changelog": "^3.4.4", - "@metamask/bridge-controller": "^0.0.0", "@metamask/network-controller": "^22.2.1", "@metamask/transaction-controller": "^46.0.0", "@types/jest": "^27.4.1", From 4053595df38ad8cdee02dbfb9604d318a74369d7 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 20:10:22 -0500 Subject: [PATCH 47/49] chore: add bridge-controller as a peer dependency for bridge-status-controller --- packages/bridge-status-controller/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 03747d635b..38c4073ccd 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -71,6 +71,7 @@ }, "peerDependencies": { "@metamask/accounts-controller": "^24.0.0", + "@metamask/bridge-controller": "^0.0.0", "@metamask/network-controller": "^22.0.0", "@metamask/transaction-controller": "^46.0.0" }, diff --git a/yarn.lock b/yarn.lock index 7799d6564d..149e56a113 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2639,6 +2639,7 @@ __metadata: typescript: "npm:~5.2.2" peerDependencies: "@metamask/accounts-controller": ^24.0.0 + "@metamask/bridge-controller": ^0.0.0 "@metamask/network-controller": ^22.0.0 "@metamask/transaction-controller": ^46.0.0 languageName: unknown From 8c7aec4f54d53ca9d8760f33ea025af1152691d1 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 22:40:32 -0500 Subject: [PATCH 48/49] refactor: extract BridgeStatusControllerGetStateAction type --- packages/bridge-status-controller/src/types.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/bridge-status-controller/src/types.ts b/packages/bridge-status-controller/src/types.ts index de34a520f3..cf64694fa7 100644 --- a/packages/bridge-status-controller/src/types.ts +++ b/packages/bridge-status-controller/src/types.ts @@ -262,14 +262,16 @@ type BridgeStatusControllerAction< handler: BridgeStatusController[FunctionName]; }; +export type BridgeStatusControllerGetStateAction = ControllerGetStateAction< + typeof BRIDGE_STATUS_CONTROLLER_NAME, + BridgeStatusControllerState +>; + // Maps to BridgeController function names type BridgeStatusControllerActions = | BridgeStatusControllerAction | BridgeStatusControllerAction - | ControllerGetStateAction< - typeof BRIDGE_STATUS_CONTROLLER_NAME, - BridgeStatusControllerState - >; + | BridgeStatusControllerGetStateAction; // Events export type BridgeStatusControllerStateChangeEvent = ControllerStateChangeEvent< From 33dab2e70823122746986c4698b6b0ae5ea0f837 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 18 Feb 2025 23:04:36 -0500 Subject: [PATCH 49/49] feat: add resetState action to BridgeStatusController and export all actions and events --- .../src/bridge-status-controller.ts | 4 ++++ .../bridge-status-controller/src/index.ts | 9 +++++++++ .../bridge-status-controller/src/types.ts | 19 +++++++++++++++---- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 8bc4a38d4c..aa12119af3 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -84,6 +84,10 @@ export class BridgeStatusController extends StaticIntervalPollingController; // Maps to BridgeController function names -type BridgeStatusControllerActions = - | BridgeStatusControllerAction - | BridgeStatusControllerAction +export type BridgeStatusControllerStartPollingForBridgeTxStatusAction = + BridgeStatusControllerAction; + +export type BridgeStatusControllerWipeBridgeStatusAction = + BridgeStatusControllerAction; + +export type BridgeStatusControllerResetStateAction = + BridgeStatusControllerAction; + +export type BridgeStatusControllerActions = + | BridgeStatusControllerStartPollingForBridgeTxStatusAction + | BridgeStatusControllerWipeBridgeStatusAction + | BridgeStatusControllerResetStateAction | BridgeStatusControllerGetStateAction; // Events @@ -289,7 +300,7 @@ export type BridgeStatusControllerBridgeTransactionFailedEvent = { payload: [{ bridgeHistoryItem: BridgeHistoryItem }]; }; -type BridgeStatusControllerEvents = +export type BridgeStatusControllerEvents = | BridgeStatusControllerStateChangeEvent | BridgeStatusControllerBridgeTransactionCompleteEvent | BridgeStatusControllerBridgeTransactionFailedEvent;