From 1b52fb8f6cc530f7aef13166d221efe5bb45cf1a Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 14:10:35 -0700 Subject: [PATCH 01/20] chore: use swap events and tx type (EVM) --- .../src/bridge-status-controller.ts | 8 ++++++-- .../bridge-status-controller/src/utils/transaction.ts | 10 ++++++++-- 2 files changed, 14 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 28586cf788d..99f26198e73 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -547,7 +547,9 @@ export class BridgeStatusController extends StaticIntervalPollingController & QuoteMetadata, approvalTxId?: string, ) => { return await this.#handleEvmTransaction( - TransactionType.bridge, + isBridgeTx ? TransactionType.bridge : TransactionType.swap, trade, quoteResponse, approvalTxId, @@ -818,6 +821,7 @@ export class BridgeStatusController extends StaticIntervalPollingController await this.#handleEvmSmartTransaction( + isBridgeTx, quoteResponse.trade as TxData, quoteResponse, approvalTxId, diff --git a/packages/bridge-status-controller/src/utils/transaction.ts b/packages/bridge-status-controller/src/utils/transaction.ts index 5befa6c38b4..dc968d0b33a 100644 --- a/packages/bridge-status-controller/src/utils/transaction.ts +++ b/packages/bridge-status-controller/src/utils/transaction.ts @@ -3,6 +3,7 @@ import type { TxData } from '@metamask/bridge-controller'; import { ChainId, formatChainIdToHex, + isCrossChain, type QuoteMetadata, type QuoteResponse, } from '@metamask/bridge-controller'; @@ -84,6 +85,11 @@ export const handleSolanaTxResponse = ( } const hexChainId = formatChainIdToHex(quoteResponse.quote.srcChainId); + const isBridgeTx = isCrossChain( + quoteResponse.quote.srcChainId, + quoteResponse.quote.destChainId, + ); + // Create a transaction meta object with bridge-specific fields return { ...getTxMetaFields(quoteResponse), @@ -92,13 +98,13 @@ export const handleSolanaTxResponse = ( chainId: hexChainId, networkClientId: snapId ?? hexChainId, txParams: { from: selectedAccountAddress, data: quoteResponse.trade }, - type: TransactionType.bridge, + type: isBridgeTx ? TransactionType.bridge : TransactionType.swap, status: TransactionStatus.submitted, hash, // Add the transaction signature as hash origin: snapId, // Add an explicit bridge flag to mark this as a Solana transaction isSolana: true, // TODO deprecate this and use chainId - isBridgeTx: true, // TODO deprecate this and use type + isBridgeTx, }; }; From e19fdc3a237fa5cecca72ccca61f0aa485a7a9b9 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 14:13:41 -0700 Subject: [PATCH 02/20] chore: don't add swap transactions to txHistory --- .../src/bridge-status-controller.ts | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 99f26198e73..96f6c986120 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -854,19 +854,21 @@ export class BridgeStatusController extends StaticIntervalPollingController Date: Mon, 19 May 2025 14:16:02 -0700 Subject: [PATCH 03/20] fix: don't restart polling for swaps --- .../src/bridge-status-controller.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 96f6c986120..bf1ad66fcc2 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -228,6 +228,14 @@ export class BridgeStatusController extends StaticIntervalPollingController { + const isBridgeTx = isCrossChain( + historyItem.quote.srcChainId, + historyItem.quote.destChainId, + ); + return isBridgeTx; }); incompleteHistoryItems.forEach((historyItem) => { From 1decbb12adf3626a01cdfd522fba7d521ba0e63e Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 14:43:59 -0700 Subject: [PATCH 04/20] chore: emit swap failed/confirmed events --- .../src/bridge-status-controller.ts | 32 +++++++++++++++++++ .../bridge-status-controller/src/types.ts | 6 +++- 2 files changed, 37 insertions(+), 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 bf1ad66fcc2..bf88c3a090b 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -175,6 +175,38 @@ export class BridgeStatusController extends StaticIntervalPollingController { + const { type, status, id } = transactionMeta; + if ( + type && + [TransactionType.bridge, TransactionType.swap].includes(type) && + ![TransactionStatus.signed, TransactionStatus.approved].includes( + status, + ) + ) { + this.#trackUnifiedSwapBridgeEvent( + UnifiedSwapBridgeEventName.Failed, + id, + ); + } + }, + ); + + this.messagingSystem.subscribe( + 'TransactionController:transactionConfirmed', + (transactionMeta) => { + const { type, id } = transactionMeta; + if (type === TransactionType.swap) { + this.#trackUnifiedSwapBridgeEvent( + UnifiedSwapBridgeEventName.Completed, + id, + ); + } + }, + ); + // 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 diff --git a/packages/bridge-status-controller/src/types.ts b/packages/bridge-status-controller/src/types.ts index 2b94314c253..f74af59db7a 100644 --- a/packages/bridge-status-controller/src/types.ts +++ b/packages/bridge-status-controller/src/types.ts @@ -26,6 +26,8 @@ import type { import type { HandleSnapRequest } from '@metamask/snaps-controllers'; import type { TransactionControllerGetStateAction, + TransactionControllerTransactionConfirmedEvent, + TransactionControllerTransactionFailedEvent, TransactionMeta, } from '@metamask/transaction-controller'; @@ -349,7 +351,9 @@ type AllowedActions = /** * The external events available to the BridgeStatusController. */ -type AllowedEvents = never; +type AllowedEvents = + | TransactionControllerTransactionFailedEvent + | TransactionControllerTransactionConfirmedEvent; /** * The messenger for the BridgeStatusController. From ebe78ae98517b43b9cb2e8042f2cc2a1bf900ee1 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 14:44:43 -0700 Subject: [PATCH 05/20] chore: remove published bridge tx events --- .../src/bridge-status-controller.ts | 9 --------- packages/bridge-status-controller/src/index.ts | 2 -- packages/bridge-status-controller/src/types.ts | 14 +------------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index bf88c3a090b..4c2cf01c784 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -413,21 +413,12 @@ export class BridgeStatusController extends StaticIntervalPollingController; -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 }]; -}; - export type BridgeStatusControllerEvents = - | BridgeStatusControllerStateChangeEvent - | BridgeStatusControllerBridgeTransactionCompleteEvent - | BridgeStatusControllerBridgeTransactionFailedEvent; + BridgeStatusControllerStateChangeEvent; /** * The external actions available to the BridgeStatusController. From d9a0246bc42e30ed9febff11c7e3d8b94a3ff72d Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 14:49:36 -0700 Subject: [PATCH 06/20] fix: update balances after bridge tx --- packages/transaction-controller/src/TransactionController.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index 2adc50a9282..9f49058bed9 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -3656,7 +3656,10 @@ export class TransactionController extends BaseController< try { const { networkClientId, type } = transactionMeta; - if (type !== TransactionType.swap) { + if ( + type && + ![TransactionType.swap, TransactionType.bridge].includes(type) + ) { return; } From e28efe5902fbddc9c56a3b9903fb923556fec0be Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 15:38:48 -0700 Subject: [PATCH 07/20] chore: subscribe to MultichainTransactionsController confirmed --- packages/bridge-status-controller/package.json | 4 +++- .../src/bridge-status-controller.ts | 13 +++++++++++++ packages/bridge-status-controller/src/types.ts | 2 ++ packages/bridge-status-controller/tsconfig.json | 1 + 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index f4badea1525..2f4cbba732f 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -50,8 +50,10 @@ "@metamask/base-controller": "^8.0.1", "@metamask/controller-utils": "^11.9.0", "@metamask/keyring-api": "^17.4.0", + "@metamask/multichain-transactions-controller": "^1.0.0", "@metamask/polling-controller": "^13.0.0", "@metamask/superstruct": "^3.1.0", + "@metamask/transaction-controller": "^56.1.0", "@metamask/user-operation-controller": "^35.0.0", "@metamask/utils": "^11.2.0", "bignumber.js": "^9.1.2", @@ -64,7 +66,6 @@ "@metamask/gas-fee-controller": "^23.0.0", "@metamask/network-controller": "^23.5.0", "@metamask/snaps-controllers": "^11.2.1", - "@metamask/transaction-controller": "^56.1.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", @@ -80,6 +81,7 @@ "@metamask/accounts-controller": "^29.0.0", "@metamask/bridge-controller": "^25.0.0", "@metamask/gas-fee-controller": "^23.0.0", + "@metamask/multichain-transactions-controller": "^1.0.0", "@metamask/network-controller": "^23.0.0", "@metamask/snaps-controllers": "^11.0.0", "@metamask/transaction-controller": "^56.0.0" diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 4c2cf01c784..aaf0de98efc 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -207,6 +207,19 @@ export class BridgeStatusController extends StaticIntervalPollingController { + const { type, id } = transactionMeta; + if (type === TransactionType.swap) { + this.#trackUnifiedSwapBridgeEvent( + UnifiedSwapBridgeEventName.Completed, + id, + ); + } + }, + ); + // 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 diff --git a/packages/bridge-status-controller/src/types.ts b/packages/bridge-status-controller/src/types.ts index 9b824a358ff..ed2ff8e6e90 100644 --- a/packages/bridge-status-controller/src/types.ts +++ b/packages/bridge-status-controller/src/types.ts @@ -18,6 +18,7 @@ import type { TxData, } from '@metamask/bridge-controller'; import type { GetGasFeeState } from '@metamask/gas-fee-controller'; +import type { MultichainTransactionsControllerTransactionConfirmedEvent } from '@metamask/multichain-transactions-controller'; import type { NetworkControllerFindNetworkClientIdByChainIdAction, NetworkControllerGetNetworkClientByIdAction, @@ -340,6 +341,7 @@ type AllowedActions = * The external events available to the BridgeStatusController. */ type AllowedEvents = + | MultichainTransactionsControllerTransactionConfirmedEvent | TransactionControllerTransactionFailedEvent | TransactionControllerTransactionConfirmedEvent; diff --git a/packages/bridge-status-controller/tsconfig.json b/packages/bridge-status-controller/tsconfig.json index 7935a0447f7..07b8a7abf74 100644 --- a/packages/bridge-status-controller/tsconfig.json +++ b/packages/bridge-status-controller/tsconfig.json @@ -12,6 +12,7 @@ { "path": "../network-controller" }, { "path": "../polling-controller" }, { "path": "../transaction-controller" }, + { "path": "../multichain-transactions-controller" }, { "path": "../gas-fee-controller" }, { "path": "../user-operation-controller" } ], From 53bdbcf5988c675be3f002f3fbaeda260ba0bd6c Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 15:40:25 -0700 Subject: [PATCH 08/20] chore: update changelogs --- packages/bridge-status-controller/CHANGELOG.md | 7 +++++++ packages/transaction-controller/CHANGELOG.md | 1 + 2 files changed, 8 insertions(+) diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 57d9dcc3041..3790787aded 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -9,13 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Subscribe to TransactionController and MultichainTransactionsController tx confirmed and failed events for swaps ([#5829](https://github.com/MetaMask/core/pull/5829)) - Error logs for invalid getTxStatus responses ([#5816](https://github.com/MetaMask/core/pull/5816)) ### Changed +- **BREAKING:** Remove the published bridgeTransactionComplete and bridgeTransactionFailed events ([#5829](https://github.com/MetaMask/core/pull/5829)) +- Modify events to use `swap` and `swapApproval` TransactionTypes when src and dest chain are the same ([#5829](https://github.com/MetaMask/core/pull/5829)) - Bump `@metamask/bridge-controller` peer dependency to `^25.0.1` ([#5811](https://github.com/MetaMask/core/pull/5811)) - Bump `@metamask/controller-utils` to `^11.9.0` ([#5812](https://github.com/MetaMask/core/pull/5812)) +### Fixed + +- Don't start or restart getTxStatus polling if transaction is a swap ([#5829](https://github.com/MetaMask/core/pull/5829)) + ## [21.0.0] ### Changed diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 6f0937efa49..7db5b2e7bc4 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Update balances after a bridge transaction ([#5829](https://github.com/MetaMask/core/pull/5829)) - Fix `addTransaction` function to correctly identify a transaction as a `simpleSend` type when the recipient is a smart account ([#5822](https://github.com/MetaMask/core/pull/5822)) ## [56.1.0] From a903aeaa3b269f1fd701342fe10c12f712ef78e8 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 15:40:46 -0700 Subject: [PATCH 09/20] chore: update yarnlock --- yarn.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/yarn.lock b/yarn.lock index 2c1cd20c0f1..a54425d674b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2748,6 +2748,7 @@ __metadata: "@metamask/controller-utils": "npm:^11.9.0" "@metamask/gas-fee-controller": "npm:^23.0.0" "@metamask/keyring-api": "npm:^17.4.0" + "@metamask/multichain-transactions-controller": "npm:^1.0.0" "@metamask/network-controller": "npm:^23.5.0" "@metamask/polling-controller": "npm:^13.0.0" "@metamask/snaps-controllers": "npm:^11.2.1" @@ -2771,6 +2772,7 @@ __metadata: "@metamask/accounts-controller": ^29.0.0 "@metamask/bridge-controller": ^25.0.0 "@metamask/gas-fee-controller": ^23.0.0 + "@metamask/multichain-transactions-controller": ^1.0.0 "@metamask/network-controller": ^23.0.0 "@metamask/snaps-controllers": ^11.0.0 "@metamask/transaction-controller": ^56.0.0 From 1dd4ed49977806f0afef04be46531d31277648f1 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 15:47:06 -0700 Subject: [PATCH 10/20] fix: package.json --- packages/bridge-status-controller/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 2f4cbba732f..c560ff83cea 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -50,10 +50,8 @@ "@metamask/base-controller": "^8.0.1", "@metamask/controller-utils": "^11.9.0", "@metamask/keyring-api": "^17.4.0", - "@metamask/multichain-transactions-controller": "^1.0.0", "@metamask/polling-controller": "^13.0.0", "@metamask/superstruct": "^3.1.0", - "@metamask/transaction-controller": "^56.1.0", "@metamask/user-operation-controller": "^35.0.0", "@metamask/utils": "^11.2.0", "bignumber.js": "^9.1.2", @@ -64,8 +62,10 @@ "@metamask/auto-changelog": "^3.4.4", "@metamask/bridge-controller": "^25.1.0", "@metamask/gas-fee-controller": "^23.0.0", + "@metamask/multichain-transactions-controller": "^1.0.0", "@metamask/network-controller": "^23.5.0", "@metamask/snaps-controllers": "^11.2.1", + "@metamask/transaction-controller": "^56.1.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", From 70bab44b72ecdb4e6c654b846e8f94d73ba005fd Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 15:50:29 -0700 Subject: [PATCH 11/20] fix: tsconfig --- packages/bridge-status-controller/tsconfig.build.json | 1 + packages/bridge-status-controller/tsconfig.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/bridge-status-controller/tsconfig.build.json b/packages/bridge-status-controller/tsconfig.build.json index 1ec93edefdf..bc350d54388 100644 --- a/packages/bridge-status-controller/tsconfig.build.json +++ b/packages/bridge-status-controller/tsconfig.build.json @@ -11,6 +11,7 @@ { "path": "../bridge-controller/tsconfig.build.json" }, { "path": "../controller-utils/tsconfig.build.json" }, { "path": "../network-controller/tsconfig.build.json" }, + { "path": "../multichain-transactions-controller/tsconfig.build.json" }, { "path": "../gas-fee-controller/tsconfig.build.json" }, { "path": "../polling-controller/tsconfig.build.json" }, { "path": "../transaction-controller/tsconfig.build.json" }, diff --git a/packages/bridge-status-controller/tsconfig.json b/packages/bridge-status-controller/tsconfig.json index 07b8a7abf74..2b313a0a49f 100644 --- a/packages/bridge-status-controller/tsconfig.json +++ b/packages/bridge-status-controller/tsconfig.json @@ -12,9 +12,9 @@ { "path": "../network-controller" }, { "path": "../polling-controller" }, { "path": "../transaction-controller" }, - { "path": "../multichain-transactions-controller" }, { "path": "../gas-fee-controller" }, - { "path": "../user-operation-controller" } + { "path": "../user-operation-controller" }, + { "path": "../multichain-transactions-controller" } ], "include": ["../../types", "./src"] } From 5e0559709269a71f225db54e86b236bde22fd3cf Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 16:13:57 -0700 Subject: [PATCH 12/20] test: mocks for subscribe call --- .../bridge-status-controller.test.ts.snap | 17 +++++++++++++++++ .../src/bridge-status-controller.test.ts | 7 +++++++ 2 files changed, 24 insertions(+) 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 index df881b505fc..169b5928e4d 100644 --- 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 @@ -120,6 +120,23 @@ Object { } `; +exports[`BridgeStatusController constructor should setup correctly 1`] = ` +Array [ + Array [ + "TransactionController:transactionFailed", + [Function], + ], + Array [ + "TransactionController:transactionConfirmed", + [Function], + ], + Array [ + "MultichainTransactionsController:transactionConfirmed", + [Function], + ], +] +`; + exports[`BridgeStatusController startPollingForBridgeTxStatus emits bridgeTransactionFailed event when the status response is failed 1`] = ` Array [ Array [ 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 a93f593c5a0..d34d6c6c4da 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -41,6 +41,7 @@ const EMPTY_INIT_STATE: BridgeStatusControllerState = { ...DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE, }; +const mockMessengerSubscribe = jest.fn(); const MockStatusResponse = { getPending: ({ srcTxHash = '0xsrcTxHash1', @@ -465,6 +466,7 @@ const getMessengerMock = ({ } return null; }), + subscribe: mockMessengerSubscribe, publish: jest.fn(), registerActionHandler: jest.fn(), registerInitialEventPayload: jest.fn(), @@ -527,6 +529,7 @@ const getController = (call: jest.Mock, traceFn?: jest.Mock) => { const controller = new BridgeStatusController({ messenger: { call, + subscribe: mockMessengerSubscribe, publish: jest.fn(), registerActionHandler: jest.fn(), registerInitialEventPayload: jest.fn(), @@ -567,6 +570,7 @@ describe('BridgeStatusController', () => { addUserOperationFromTransactionFn: jest.fn(), }); expect(bridgeStatusController.state).toStrictEqual(EMPTY_INIT_STATE); + expect(mockMessengerSubscribe.mock.calls).toMatchSnapshot(); }); it('rehydrates the tx history state', async () => { // Setup @@ -993,6 +997,7 @@ describe('BridgeStatusController', () => { } return null; }), + subscribe: mockMessengerSubscribe, publish: jest.fn(), registerActionHandler: jest.fn(), registerInitialEventPayload: jest.fn(), @@ -1079,6 +1084,7 @@ describe('BridgeStatusController', () => { } return null; }), + subscribe: mockMessengerSubscribe, publish: jest.fn(), registerActionHandler: jest.fn(), registerInitialEventPayload: jest.fn(), @@ -1179,6 +1185,7 @@ describe('BridgeStatusController', () => { } return null; }), + subscribe: mockMessengerSubscribe, publish: jest.fn(), registerActionHandler: jest.fn(), registerInitialEventPayload: jest.fn(), From 5bc748054159dcd42671da801d7504da1010214b Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 16:21:59 -0700 Subject: [PATCH 13/20] test: remove publish tests --- .../src/bridge-status-controller.test.ts | 23 +------------------ 1 file changed, 1 insertion(+), 22 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 d34d6c6c4da..595e05981a3 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -813,17 +813,6 @@ describe('BridgeStatusController', () => { // Assertions expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); - expect(messengerMock.publish).toHaveBeenCalledWith( - 'BridgeStatusController:bridgeTransactionComplete', - { - bridgeHistoryItem: expect.objectContaining({ - txMetaId: 'bridgeTxMetaId1', - status: expect.objectContaining({ - status: 'COMPLETE', - }), - }), - }, - ); // Cleanup jest.restoreAllMocks(); @@ -859,17 +848,6 @@ describe('BridgeStatusController', () => { // Assertions expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); - expect(messengerMock.publish).toHaveBeenCalledWith( - 'BridgeStatusController:bridgeTransactionFailed', - { - bridgeHistoryItem: expect.objectContaining({ - txMetaId: 'bridgeTxMetaId1', - status: expect.objectContaining({ - status: 'FAILED', - }), - }), - }, - ); expect(messengerMock.call.mock.calls).toMatchSnapshot(); // Cleanup @@ -909,6 +887,7 @@ describe('BridgeStatusController', () => { } return null; }), + subscribe: mockMessengerSubscribe, publish: jest.fn(), registerActionHandler: jest.fn(), registerInitialEventPayload: jest.fn(), From 1805bcdaafb42f7a07bf011c1017d38b74bf904f Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 16:23:01 -0700 Subject: [PATCH 14/20] chore: add validator test for swap txStatus --- .../src/utils/validators.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/bridge-status-controller/src/utils/validators.test.ts b/packages/bridge-status-controller/src/utils/validators.test.ts index 17ec15017ff..80d23811d0c 100644 --- a/packages/bridge-status-controller/src/utils/validators.test.ts +++ b/packages/bridge-status-controller/src/utils/validators.test.ts @@ -246,6 +246,17 @@ describe('validators', () => { input: BridgeTxStatusResponses.STATUS_FAILED_VALID, description: 'valid failed bridge status', }, + { + input: { + status: 'COMPLETE', + srcChain: { + chainId: 1151111081099710, + txHash: + '33LfknAQsrLC1WzmNybkZWUtuGANRFHNupsQ1YLCnjXGXxbBE93BbVTeKLLdE7Sz3WUdxnFW5HQhPuUayrXyqWky', + }, + }, + description: 'placeholder complete swap status', + }, ])( 'should not throw for valid response for $description', ({ input }: { input: unknown }) => { From ec78a01d9aea30041767cc82142eef2773e2b80b Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 19 May 2025 17:55:21 -0700 Subject: [PATCH 15/20] test: tx status subscription tests --- .../bridge-status-controller.test.ts.snap | 155 +++++++++ .../src/bridge-status-controller.test.ts | 301 +++++++++++++++++- 2 files changed, 451 insertions(+), 5 deletions(-) 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 index 169b5928e4d..3665eb9b474 100644 --- 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 @@ -2255,6 +2255,161 @@ Object { } `; +exports[`BridgeStatusController subscription handlers MultichainTransactionsController:transactionConfirmed should track completed event for other transaction types 1`] = `Array []`; + +exports[`BridgeStatusController subscription handlers MultichainTransactionsController:transactionConfirmed should track completed event for swap transaction 1`] = ` +Array [ + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Completed", + Object {}, + ], +] +`; + +exports[`BridgeStatusController subscription handlers TransactionController:transactionConfirmed should not track completed event for other transaction types 1`] = `Array []`; + +exports[`BridgeStatusController subscription handlers TransactionController:transactionConfirmed should track completed event for swap transaction 1`] = ` +Array [ + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Completed", + Object { + "action_type": "swapbridge-v1", + "actual_time_minutes": 0, + "allowance_reset_transaction": undefined, + "approval_transaction": undefined, + "chain_id_destination": "eip155:42161", + "chain_id_source": "eip155:42161", + "custom_slippage": true, + "destination_transaction": "PENDING", + "error_message": "error_message", + "gas_included": false, + "is_hardware_wallet": false, + "price_impact": 0, + "provider": "lifi_across", + "quote_vs_execution_ratio": 1, + "quoted_time_minutes": 0.25, + "quoted_vs_used_gas_ratio": 1, + "security_warnings": Array [], + "slippage_limit": 0, + "source_transaction": "COMPLETE", + "stx_enabled": false, + "swap_type": "single_chain", + "token_address_destination": "eip155:42161/slip44:60", + "token_address_source": "eip155:42161/slip44:60", + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_actual_gas": 0, + "usd_actual_return": 0, + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], +] +`; + +exports[`BridgeStatusController subscription handlers TransactionController:transactionFailed should not track failed event for approved status 1`] = `Array []`; + +exports[`BridgeStatusController subscription handlers TransactionController:transactionFailed should not track failed event for other transaction types 1`] = `Array []`; + +exports[`BridgeStatusController subscription handlers TransactionController:transactionFailed should not track failed event for signed status 1`] = `Array []`; + +exports[`BridgeStatusController subscription handlers TransactionController:transactionFailed should track failed event for bridge transaction 1`] = ` +Array [ + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Failed", + Object { + "action_type": "crosschain-v1", + "actual_time_minutes": 0, + "allowance_reset_transaction": undefined, + "approval_transaction": undefined, + "chain_id_destination": "eip155:10", + "chain_id_source": "eip155:42161", + "custom_slippage": true, + "destination_transaction": "PENDING", + "error_message": "error_message", + "gas_included": false, + "is_hardware_wallet": false, + "price_impact": 0, + "provider": "lifi_across", + "quote_vs_execution_ratio": 1, + "quoted_time_minutes": 0.25, + "quoted_vs_used_gas_ratio": 1, + "security_warnings": Array [], + "slippage_limit": 0, + "source_transaction": "COMPLETE", + "stx_enabled": false, + "swap_type": "crosschain", + "token_address_destination": "eip155:10/slip44:60", + "token_address_source": "eip155:42161/slip44:60", + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_actual_gas": 0, + "usd_actual_return": 0, + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], +] +`; + +exports[`BridgeStatusController subscription handlers TransactionController:transactionFailed should track failed event for swap transaction 1`] = ` +Array [ + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Failed", + Object { + "action_type": "swapbridge-v1", + "actual_time_minutes": 0, + "allowance_reset_transaction": undefined, + "approval_transaction": undefined, + "chain_id_destination": "eip155:42161", + "chain_id_source": "eip155:42161", + "custom_slippage": true, + "destination_transaction": "PENDING", + "error_message": "error_message", + "gas_included": false, + "is_hardware_wallet": false, + "price_impact": 0, + "provider": "lifi_across", + "quote_vs_execution_ratio": 1, + "quoted_time_minutes": 0.25, + "quoted_vs_used_gas_ratio": 1, + "security_warnings": Array [], + "slippage_limit": 0, + "source_transaction": "COMPLETE", + "stx_enabled": false, + "swap_type": "single_chain", + "token_address_destination": "eip155:42161/slip44:60", + "token_address_source": "eip155:42161/slip44:60", + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_actual_gas": 0, + "usd_actual_return": 0, + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], +] +`; + exports[`BridgeStatusController wipeBridgeStatus wipes the bridge status for the given address 1`] = ` Array [ Array [ 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 595e05981a3..43ea9eab209 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -1,20 +1,42 @@ /* eslint-disable jest/no-conditional-in-test */ /* eslint-disable jest/no-restricted-matchers */ +import type { AccountsControllerActions } from '@metamask/accounts-controller'; +import { Messenger } from '@metamask/base-controller'; +import type { + BridgeControllerActions, + BridgeControllerEvents, +} from '@metamask/bridge-controller'; import { type QuoteResponse, type QuoteMetadata, StatusTypes, + BridgeController, } from '@metamask/bridge-controller'; import { ChainId } from '@metamask/bridge-controller'; import { ActionTypes, FeeType } from '@metamask/bridge-controller'; -import { EthAccountType } from '@metamask/keyring-api'; -import { TransactionType } from '@metamask/transaction-controller'; -import type { TransactionMeta } from '@metamask/transaction-controller'; +import { EthAccountType, SolScope } from '@metamask/keyring-api'; +import { + TransactionType, + TransactionStatus, +} from '@metamask/transaction-controller'; +import type { + TransactionControllerActions, + TransactionControllerEvents, + TransactionMeta, + TransactionParams, +} from '@metamask/transaction-controller'; import type { CaipAssetType } from '@metamask/utils'; import { numberToHex } from '@metamask/utils'; import { BridgeStatusController } from './bridge-status-controller'; -import { DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE } from './constants'; +import { + BRIDGE_STATUS_CONTROLLER_NAME, + DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE, +} from './constants'; +import type { + BridgeStatusControllerActions, + BridgeStatusControllerEvents, +} from './types'; import { type BridgeId, type StartPollingForBridgeTxStatusArgsSerialized, @@ -26,6 +48,8 @@ import { import * as bridgeStatusUtils from './utils/bridge-status'; import * as transactionUtils from './utils/transaction'; import { flushPromises } from '../../../tests/helpers'; +import { CHAIN_IDS } from '../../bridge-controller/src/constants/chains'; +import type { MultichainTransactionsControllerEvents } from '../../multichain-transactions-controller/src/MultichainTransactionsController'; jest.mock('uuid', () => ({ v4: () => 'test-uuid-1234', @@ -266,7 +290,7 @@ const getMockStartPollingForBridgeTxStatusArgs = ({ to: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', from: account, value: '0x038d7ea4c68000', - data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038589602234000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000007f544a44c0000000000000000000000000056ca675c3633cc16bd6849e2b431d4e8de5e23bf000000000000000000000000000000000000000000000000000000000000006c5a39b10a4f4f0747826140d2c5fe6ef47965741f6f7a4734bf784bf3ae3f24520000000a000222266cc2dca0671d2a17ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b7100000000000000000000000000000000000000009ce3c510b3f58edc8d53ae708056e30926f62d0b42d5c9b61c391bb4e8a2c1917f8ed995169ffad0d79af2590303e83c57e15a9e0b248679849556c2e03a1c811b', + data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038589602234000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000007f544a44c0000000000000000000000000056ca675c3633cc16bd6849e2b431d4e8de5e23bf000000000000000000000000000000000000000000000000000000000000006c5a39b10a4f4f0747826140d2c5fe6ef47965741f6f7a4734bf784bf3ae3f24520000000a000222266cc2dca0671d2a17ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b7100000000000000000000000000000000000000009ce3c510b3f58edc8d53ae708056e30926f62d0b42d5c9b61c391bb4e8a2c1917f8ed995169ffad0d79af2590303e83c57e15a9e0b248679849556c2e03a1c811b', gasLimit: 282915, }, approval: null, @@ -399,6 +423,38 @@ const MockTxHistory = { completionTime: undefined, }, }), + getPendingSwap: ({ + txMetaId = 'swapTxMetaId1', + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 42161, + } = {}): 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, + }, + approvalTxId: undefined, + isStxEnabled: false, + hasApprovalTx: false, + completionTime: undefined, + }, + }), getComplete: ({ txMetaId = 'bridgeTxMetaId1', srcTxHash = '0xsrcTxHash1', @@ -610,6 +666,7 @@ describe('BridgeStatusController', () => { txHistory: { ...MockTxHistory.getPending(), ...MockTxHistory.getUnknown(), + ...MockTxHistory.getPendingSwap(), }, }, clientId: BridgeClientId.EXTENSION, @@ -741,6 +798,7 @@ describe('BridgeStatusController', () => { } return null; }), + subscribe: mockMessengerSubscribe, publish: jest.fn(), registerActionHandler: jest.fn(), registerInitialEventPayload: jest.fn(), @@ -1862,4 +1920,237 @@ describe('BridgeStatusController', () => { expect(mockTraceFn.mock.calls).toMatchSnapshot(); }); }); + + describe('subscription handlers', () => { + let mockBridgeStatusMessenger: jest.Mocked; + let mockTrackEventFn: jest.Mock; + + let mockMessenger: Messenger< + | BridgeStatusControllerActions + | TransactionControllerActions + | BridgeControllerActions + | AccountsControllerActions, + | BridgeStatusControllerEvents + | TransactionControllerEvents + | BridgeControllerEvents + | MultichainTransactionsControllerEvents + >; + + beforeEach(() => { + mockMessenger = new Messenger< + | BridgeStatusControllerActions + | TransactionControllerActions + | BridgeControllerActions + | AccountsControllerActions, + | BridgeStatusControllerEvents + | TransactionControllerEvents + | BridgeControllerEvents + | MultichainTransactionsControllerEvents + >(); + + jest.spyOn(mockMessenger, 'call').mockImplementation((...args) => { + console.log('call', args); + return Promise.resolve(); + }); + + mockBridgeStatusMessenger = mockMessenger.getRestricted({ + name: BRIDGE_STATUS_CONTROLLER_NAME, + allowedActions: [ + 'TransactionController:getState', + 'BridgeController:trackUnifiedSwapBridgeEvent', + 'AccountsController:getAccountByAddress', + ], + allowedEvents: [ + 'TransactionController:transactionFailed', + 'TransactionController:transactionConfirmed', + 'MultichainTransactionsController:transactionConfirmed', + ], + }) as never; + + const mockBridgeMessenger = mockMessenger.getRestricted({ + name: 'BridgeController', + allowedActions: [], + allowedEvents: [], + }); + mockTrackEventFn = jest.fn(); + new BridgeController({ + messenger: mockBridgeMessenger, + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), + trackMetaMetricsFn: mockTrackEventFn, + getLayer1GasFee: jest.fn(), + }); + + new BridgeStatusController({ + messenger: mockBridgeStatusMessenger, + clientId: BridgeClientId.EXTENSION, + fetchFn: jest.fn(), + addTransactionFn: jest.fn(), + estimateGasFeeFn: jest.fn(), + addUserOperationFromTransactionFn: jest.fn(), + state: { + txHistory: { + ...MockTxHistory.getPending(), + ...MockTxHistory.getPendingSwap(), + }, + }, + }); + }); + + describe('TransactionController:transactionFailed', () => { + it('should track failed event for bridge transaction', () => { + const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); + mockMessenger.publish('TransactionController:transactionFailed', { + error: 'tx-error', + transactionMeta: { + chainId: CHAIN_IDS.ARBITRUM, + networkClientId: 'eth-id', + time: Date.now(), + txParams: {} as unknown as TransactionParams, + type: TransactionType.bridge, + status: TransactionStatus.failed, + id: 'bridgeTxMetaId1', + }, + }); + + expect(messengerCallSpy.mock.calls).toMatchSnapshot(); + }); + + it('should track failed event for swap transaction', () => { + const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); + mockMessenger.publish('TransactionController:transactionFailed', { + error: 'tx-error', + transactionMeta: { + chainId: CHAIN_IDS.ARBITRUM, + networkClientId: 'eth-id', + time: Date.now(), + txParams: {} as unknown as TransactionParams, + type: TransactionType.swap, + status: TransactionStatus.failed, + id: 'swapTxMetaId1', + }, + }); + + expect(messengerCallSpy.mock.calls).toMatchSnapshot(); + }); + + it('should not track failed event for signed status', () => { + const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); + mockMessenger.publish('TransactionController:transactionFailed', { + error: 'tx-error', + transactionMeta: { + chainId: CHAIN_IDS.ARBITRUM, + networkClientId: 'eth-id', + time: Date.now(), + txParams: {} as unknown as TransactionParams, + type: TransactionType.swap, + status: TransactionStatus.signed, + id: 'swapTxMetaId1', + }, + }); + + expect(messengerCallSpy.mock.calls).toMatchSnapshot(); + }); + + it('should not track failed event for approved status', () => { + const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); + mockMessenger.publish('TransactionController:transactionFailed', { + error: 'tx-error', + transactionMeta: { + chainId: CHAIN_IDS.ARBITRUM, + networkClientId: 'eth-id', + time: Date.now(), + txParams: {} as unknown as TransactionParams, + type: TransactionType.swap, + status: TransactionStatus.approved, + id: 'swapTxMetaId1', + }, + }); + + expect(messengerCallSpy.mock.calls).toMatchSnapshot(); + }); + + it('should not track failed event for other transaction types', () => { + const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); + mockMessenger.publish('TransactionController:transactionFailed', { + error: 'tx-error', + transactionMeta: { + chainId: CHAIN_IDS.ARBITRUM, + networkClientId: 'eth-id', + time: Date.now(), + txParams: {} as unknown as TransactionParams, + type: TransactionType.simpleSend, + status: TransactionStatus.failed, + id: 'simpleSendTxMetaId1', + }, + }); + + expect(messengerCallSpy.mock.calls).toMatchSnapshot(); + }); + }); + + describe('TransactionController:transactionConfirmed', () => { + it('should track completed event for swap transaction', () => { + const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); + mockMessenger.publish('TransactionController:transactionConfirmed', { + chainId: CHAIN_IDS.ARBITRUM, + networkClientId: 'eth-id', + time: Date.now(), + txParams: {} as unknown as TransactionParams, + type: TransactionType.swap, + status: TransactionStatus.confirmed, + id: 'swapTxMetaId1', + }); + + expect(messengerCallSpy.mock.calls).toMatchSnapshot(); + }); + + it('should not track completed event for other transaction types', () => { + const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); + mockMessenger.publish('TransactionController:transactionConfirmed', { + chainId: CHAIN_IDS.ARBITRUM, + networkClientId: 'eth-id', + time: Date.now(), + txParams: {} as unknown as TransactionParams, + type: TransactionType.bridge, + status: TransactionStatus.confirmed, + id: 'bridgeTxMetaId1', + }); + + expect(messengerCallSpy.mock.calls).toMatchSnapshot(); + }); + }); + + describe('MultichainTransactionsController:transactionConfirmed', () => { + it('should track completed event for swap transaction', () => { + const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); + mockMessenger.publish( + 'MultichainTransactionsController:transactionConfirmed', + { + chain: SolScope.Mainnet, + type: 'swap', + status: TransactionStatus.confirmed, + id: 'swapTxMetaId100', + } as never, // TODO remove never + ); + + expect(messengerCallSpy.mock.calls).toMatchSnapshot(); + }); + + it('should track completed event for other transaction types', () => { + const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); + mockMessenger.publish( + 'MultichainTransactionsController:transactionConfirmed', + { + chain: SolScope.Mainnet, + type: 'bridge:send', + status: TransactionStatus.confirmed, + id: 'bridgeTxMetaId1', + } as never, + ); + + expect(messengerCallSpy.mock.calls).toMatchSnapshot(); + }); + }); + }); }); From c24b2621aa09e231d2f5b92dffbdd1e342f7dad3 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 20 May 2025 09:34:50 -0700 Subject: [PATCH 16/20] test: more event coverage --- .../bridge-status-controller.test.ts.snap | 37 +++++++++- .../src/bridge-status-controller.test.ts | 67 +++++++++++++++++-- 2 files changed, 99 insertions(+), 5 deletions(-) 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 index 3665eb9b474..eb35f85b621 100644 --- 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 @@ -2259,10 +2259,45 @@ exports[`BridgeStatusController subscription handlers MultichainTransactionsCont exports[`BridgeStatusController subscription handlers MultichainTransactionsController:transactionConfirmed should track completed event for swap transaction 1`] = ` Array [ + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], Array [ "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Completed", - Object {}, + Object { + "action_type": "swapbridge-v1", + "actual_time_minutes": 0, + "allowance_reset_transaction": undefined, + "approval_transaction": undefined, + "chain_id_destination": "eip155:42161", + "chain_id_source": "eip155:42161", + "custom_slippage": true, + "destination_transaction": "PENDING", + "error_message": "error_message", + "gas_included": false, + "is_hardware_wallet": false, + "price_impact": 0, + "provider": "lifi_across", + "quote_vs_execution_ratio": 1, + "quoted_time_minutes": 0.25, + "quoted_vs_used_gas_ratio": 1, + "security_warnings": Array [], + "slippage_limit": 0, + "source_transaction": "COMPLETE", + "stx_enabled": false, + "swap_type": "single_chain", + "token_address_destination": "eip155:42161/slip44:60", + "token_address_source": "eip155:42161/slip44:60", + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_actual_gas": 0, + "usd_actual_return": 0, + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, ], ] `; 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 43ea9eab209..a57a741c768 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -11,6 +11,7 @@ import { type QuoteMetadata, StatusTypes, BridgeController, + getNativeAssetForChainId, } from '@metamask/bridge-controller'; import { ChainId } from '@metamask/bridge-controller'; import { ActionTypes, FeeType } from '@metamask/bridge-controller'; @@ -2127,11 +2128,40 @@ describe('BridgeStatusController', () => { mockMessenger.publish( 'MultichainTransactionsController:transactionConfirmed', { + from: { + address: 'address-id', + asset: { + type: getNativeAssetForChainId(SolScope.Mainnet).assetId, + fungible: true, + unit: 'SOL', + amount: '1000', + }, + } as never, chain: SolScope.Mainnet, type: 'swap', status: TransactionStatus.confirmed, - id: 'swapTxMetaId100', - } as never, // TODO remove never + id: 'swapTxMetaId1', + account: 'test-account-id', + timestamp: Date.now(), + to: [{ address: 'to-address', asset: null }], + fees: [ + { + type: 'base', + asset: { + type: getNativeAssetForChainId(SolScope.Mainnet).assetId, + fungible: true, + unit: 'SOL', + amount: '1000', + }, + }, + ], + events: [ + { + status: 'confirmed', + timestamp: Date.now(), + }, + ], + }, ); expect(messengerCallSpy.mock.calls).toMatchSnapshot(); @@ -2142,11 +2172,40 @@ describe('BridgeStatusController', () => { mockMessenger.publish( 'MultichainTransactionsController:transactionConfirmed', { + from: { + address: 'address-id', + asset: { + type: getNativeAssetForChainId(SolScope.Mainnet).assetId, + fungible: true, + unit: 'SOL', + amount: '1000', + }, + } as never, chain: SolScope.Mainnet, type: 'bridge:send', status: TransactionStatus.confirmed, - id: 'bridgeTxMetaId1', - } as never, + id: 'bridgeTxMetaId100', + account: 'test-account-id', + timestamp: Date.now(), + to: [{ address: 'to-address', asset: null }], + fees: [ + { + type: 'base', + asset: { + type: getNativeAssetForChainId(SolScope.Mainnet).assetId, + fungible: true, + unit: 'SOL', + amount: '1000', + }, + }, + ], + events: [ + { + status: 'confirmed', + timestamp: Date.now(), + }, + ], + }, ); expect(messengerCallSpy.mock.calls).toMatchSnapshot(); From aa03114e7b1a495743e04f18ad87311c9def2d0e Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 20 May 2025 09:35:24 -0700 Subject: [PATCH 17/20] fix: add swaps to txHistory --- .../src/bridge-status-controller.ts | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index aaf0de98efc..0e731717485 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -294,12 +294,7 @@ export class BridgeStatusController extends StaticIntervalPollingController { const { @@ -349,10 +344,29 @@ export class BridgeStatusController extends StaticIntervalPollingController { + const { quoteResponse, bridgeTxMeta } = txHistoryMeta; + + this.#addTxToHistory(txHistoryMeta); + + const isBridgeTx = isCrossChain( + quoteResponse.quote.srcChainId, + quoteResponse.quote.destChainId, + ); + if (isBridgeTx) { + this.#pollingTokensByTxMetaId[bridgeTxMeta.id] = this.startPolling({ + bridgeTxMetaId: bridgeTxMeta.id, + }); + } }; // This will be called after you call this.startPolling() @@ -898,21 +912,19 @@ export class BridgeStatusController extends StaticIntervalPollingController Date: Tue, 20 May 2025 16:01:28 -0700 Subject: [PATCH 18/20] test: add swap tests --- .../bridge-status-controller.test.ts.snap | 994 +++++++++++++++++- .../src/bridge-status-controller.test.ts | 260 ++++- 2 files changed, 1221 insertions(+), 33 deletions(-) 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 index eb35f85b621..e291a32e8ca 100644 --- 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 @@ -353,7 +353,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should delay after submitting linea approval 1`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should delay after submitting linea approval 1`] = ` Object { "chainId": "0xa4b1", "hash": "0xevmTxHash", @@ -372,7 +372,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should delay after submitting linea approval 2`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should delay after submitting linea approval 2`] = ` Object { "bridge": "across", "bridgeId": "lifi", @@ -473,7 +473,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should delay after submitting linea approval 3`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should delay after submitting linea approval 3`] = ` Array [ Array [ "AccountsController:getAccountByAddress", @@ -549,7 +549,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should delay after submitting linea approval 4`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should delay after submitting linea approval 4`] = ` Array [ Array [ Object { @@ -574,7 +574,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should handle smart accounts (4337) 1`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should handle smart accounts (4337) 1`] = ` Object { "chainId": "0xa4b1", "hash": "0xevmTxHash", @@ -593,7 +593,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should handle smart accounts (4337) 2`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should handle smart accounts (4337) 2`] = ` Object { "bridge": "across", "bridgeId": "lifi", @@ -694,7 +694,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should handle smart accounts (4337) 3`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should handle smart accounts (4337) 3`] = ` Array [ Array [ Object { @@ -714,7 +714,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should handle smart accounts (4337) 4`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should handle smart accounts (4337) 4`] = ` Array [ Array [ "AccountsController:getAccountByAddress", @@ -776,7 +776,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should handle smart accounts (4337) 5`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should handle smart accounts (4337) 5`] = ` Array [ Array [ Object { @@ -801,7 +801,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should handle smart transactions 1`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should handle smart transactions 1`] = ` Object { "approvalTxId": undefined, "chainId": "0xa4b1", @@ -831,7 +831,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should handle smart transactions 2`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should handle smart transactions 2`] = ` Object { "bridge": "across", "bridgeId": "lifi", @@ -932,7 +932,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should handle smart transactions 3`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should handle smart transactions 3`] = ` Array [ Array [ Object { @@ -952,7 +952,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should handle smart transactions 4`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should handle smart transactions 4`] = ` Array [ Array [ Object { @@ -977,7 +977,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should handle smart transactions 5`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should handle smart transactions 5`] = ` Array [ Array [ "AccountsController:getAccountByAddress", @@ -1036,7 +1036,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should reset USDT allowance 1`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance 1`] = ` Object { "chainId": "0xa4b1", "hash": "0xevmTxHash", @@ -1055,7 +1055,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should reset USDT allowance 2`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance 2`] = ` Object { "bridge": "across", "bridgeId": "lifi", @@ -1156,7 +1156,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should reset USDT allowance 3`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance 3`] = ` Array [ Array [ Object { @@ -1206,7 +1206,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should reset USDT allowance 4`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance 4`] = ` Array [ Array [ Object { @@ -1271,7 +1271,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should reset USDT allowance 5`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance 5`] = ` Array [ Array [ "BridgeController:getBridgeERC20Allowance", @@ -1366,7 +1366,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should successfully submit an EVM bridge transaction with approval 1`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with approval 1`] = ` Object { "chainId": "0xa4b1", "hash": "0xevmTxHash", @@ -1385,7 +1385,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should successfully submit an EVM bridge transaction with approval 2`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with approval 2`] = ` Object { "bridge": "across", "bridgeId": "lifi", @@ -1486,7 +1486,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should successfully submit an EVM bridge transaction with approval 3`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with approval 3`] = ` Array [ Array [ Object { @@ -1531,7 +1531,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should successfully submit an EVM bridge transaction with approval 4`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with approval 4`] = ` Array [ Array [ "AccountsController:getAccountByAddress", @@ -1607,7 +1607,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should successfully submit an EVM bridge transaction with no approval 1`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with no approval 1`] = ` Object { "chainId": "0xa4b1", "hash": "0xevmTxHash", @@ -1626,7 +1626,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should successfully submit an EVM bridge transaction with no approval 2`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with no approval 2`] = ` Object { "bridge": "across", "bridgeId": "lifi", @@ -1727,7 +1727,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: EVM should successfully submit an EVM bridge transaction with no approval 3`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with no approval 3`] = ` Array [ Array [ Object { @@ -1747,7 +1747,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should successfully submit an EVM bridge transaction with no approval 4`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with no approval 4`] = ` Array [ Array [ Object { @@ -1772,7 +1772,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should successfully submit an EVM bridge transaction with no approval 5`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with no approval 5`] = ` Array [ Array [ "AccountsController:getAccountByAddress", @@ -1834,7 +1834,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should throw an error if approval tx fails 1`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should throw an error if approval tx fails 1`] = ` Array [ Array [ Object { @@ -1859,7 +1859,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should throw an error if approval tx fails 2`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should throw an error if approval tx fails 2`] = ` Array [ Array [ "AccountsController:getAccountByAddress", @@ -1875,7 +1875,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should throw an error if approval tx meta is undefined 1`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should throw an error if approval tx meta is undefined 1`] = ` Array [ Array [ Object { @@ -1900,7 +1900,7 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM should throw an error if approval tx meta is undefined 2`] = ` +exports[`BridgeStatusController submitTx: EVM bridge should throw an error if approval tx meta is undefined 2`] = ` Array [ Array [ "AccountsController:getAccountByAddress", @@ -1919,6 +1919,936 @@ Array [ ] `; +exports[`BridgeStatusController submitTx: EVM swap should handle smart accounts (4337) 1`] = ` +Object { + "chainId": "0xa4b1", + "hash": "0xevmTxHash", + "id": "test-tx-id", + "status": "unapproved", + "time": 1234567890, + "txParams": Object { + "chainId": "0xa4b1", + "data": "0xdata", + "from": "0xaccount1", + "gasLimit": "0x5208", + "to": "0xbridgeContract", + "value": "0x0", + }, + "type": "swap", +} +`; + +exports[`BridgeStatusController submitTx: EVM swap should handle smart accounts (4337) 2`] = ` +Object { + "bridge": "across", + "bridgeId": "lifi", + "destChainId": 42161, + "quote": Object { + "bridgeId": "lifi", + "bridges": Array [ + "across", + ], + "destAsset": Object { + "address": "0x0000000000000000000000000000000000000000", + "assetId": "eip155:10/slip44:60", + "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": 42161, + "destTokenAmount": "990654755978612", + "feeData": Object { + "metabridge": Object { + "amount": "8750000000000", + "asset": Object { + "address": "0x0000000000000000000000000000000000000000", + "assetId": "eip155:42161/slip44:60", + "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", + "assetId": "eip155:42161/slip44:60", + "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", + "assetId": "eip155:10/slip44:60", + "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", + "assetId": "eip155:42161/slip44:60", + "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, + }, + ], + }, + "refuel": false, + "srcChainId": 42161, + "srcTxHash": "0xevmTxHash", +} +`; + +exports[`BridgeStatusController submitTx: EVM swap should handle smart accounts (4337) 3`] = ` +Array [ + Array [ + Object { + "chainId": "0xa4b1", + "networkClientId": "arbitrum", + "transactionParams": Object { + "chainId": "0xa4b1", + "data": "0xdata", + "from": "0xaccount1", + "gas": "21000", + "gasLimit": "21000", + "to": "0xbridgeContract", + "value": "0x0", + }, + }, + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM swap should handle smart accounts (4337) 4`] = ` +Array [ + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "NetworkController:findNetworkClientIdByChainId", + "0xa4b1", + ], + Array [ + "GasFeeController:getState", + ], + Array [ + "TransactionController:getState", + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], + Array [ + "AccountsController:getAccountByAddress", + "", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "action_type": "swapbridge-v1", + "actual_time_minutes": 0, + "allowance_reset_transaction": undefined, + "approval_transaction": undefined, + "chain_id_destination": "eip155:42161", + "chain_id_source": "eip155:42161", + "custom_slippage": true, + "destination_transaction": "PENDING", + "error_message": "error_message", + "gas_included": false, + "is_hardware_wallet": false, + "price_impact": 0, + "provider": "lifi_across", + "quote_vs_execution_ratio": 1, + "quoted_time_minutes": 0, + "quoted_vs_used_gas_ratio": 1, + "security_warnings": Array [], + "slippage_limit": 0, + "source_transaction": "COMPLETE", + "stx_enabled": false, + "swap_type": "single_chain", + "token_address_destination": "eip155:10/slip44:60", + "token_address_source": "eip155:42161/slip44:60", + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_actual_gas": 0, + "usd_actual_return": 0, + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM swap should handle smart accounts (4337) 5`] = ` +Array [ + Array [ + Object { + "chainId": "0xa4b1", + "data": "0xdata", + "from": "0xaccount1", + "gas": "0x5208", + "gasLimit": "21000", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, + "to": "0xbridgeContract", + "value": "0x0", + }, + Object { + "actionId": "1234567890.456", + "networkClientId": "arbitrum", + "origin": "metamask", + "requireApproval": false, + "type": "bridge", + }, + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM swap should handle smart transactions 1`] = ` +Object { + "approvalTxId": undefined, + "chainId": "0xa4b1", + "destinationChainId": "0xa4b1", + "destinationTokenAddress": "0x0000000000000000000000000000000000000000", + "destinationTokenAmount": "990654755978612", + "destinationTokenDecimals": 18, + "destinationTokenSymbol": "ETH", + "hash": "0xevmTxHash", + "id": "test-tx-id", + "sourceTokenAddress": "0x0000000000000000000000000000000000000000", + "sourceTokenAmount": "991250000000000", + "sourceTokenDecimals": 18, + "sourceTokenSymbol": "ETH", + "status": "unapproved", + "swapTokenValue": "1.234", + "time": 1234567890, + "txParams": Object { + "chainId": "0xa4b1", + "data": "0xdata", + "from": "0xaccount1", + "gasLimit": "0x5208", + "to": "0xbridgeContract", + "value": "0x0", + }, + "type": "swap", +} +`; + +exports[`BridgeStatusController submitTx: EVM swap should handle smart transactions 2`] = ` +Object { + "bridge": "across", + "bridgeId": "lifi", + "destChainId": 42161, + "quote": Object { + "bridgeId": "lifi", + "bridges": Array [ + "across", + ], + "destAsset": Object { + "address": "0x0000000000000000000000000000000000000000", + "assetId": "eip155:10/slip44:60", + "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": 42161, + "destTokenAmount": "990654755978612", + "feeData": Object { + "metabridge": Object { + "amount": "8750000000000", + "asset": Object { + "address": "0x0000000000000000000000000000000000000000", + "assetId": "eip155:42161/slip44:60", + "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", + "assetId": "eip155:42161/slip44:60", + "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", + "assetId": "eip155:10/slip44:60", + "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", + "assetId": "eip155:42161/slip44:60", + "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, + }, + ], + }, + "refuel": false, + "srcChainId": 42161, + "srcTxHash": "0xevmTxHash", +} +`; + +exports[`BridgeStatusController submitTx: EVM swap should handle smart transactions 3`] = ` +Array [ + Array [ + Object { + "chainId": "0xa4b1", + "networkClientId": "arbitrum", + "transactionParams": Object { + "chainId": "0xa4b1", + "data": "0xdata", + "from": "0xaccount1", + "gas": "21000", + "gasLimit": "21000", + "to": "0xbridgeContract", + "value": "0x0", + }, + }, + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM swap should handle smart transactions 4`] = ` +Array [ + Array [ + Object { + "chainId": "0xa4b1", + "data": "0xdata", + "from": "0xaccount1", + "gas": "0x5208", + "gasLimit": "21000", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, + "to": "0xbridgeContract", + "value": "0x0", + }, + Object { + "actionId": "1234567890.456", + "networkClientId": "arbitrum", + "origin": "metamask", + "requireApproval": false, + "type": "swap", + }, + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM swap should handle smart transactions 5`] = ` +Array [ + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "NetworkController:findNetworkClientIdByChainId", + "0xa4b1", + ], + Array [ + "GasFeeController:getState", + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], + Array [ + "AccountsController:getAccountByAddress", + "", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "action_type": "swapbridge-v1", + "actual_time_minutes": 0, + "allowance_reset_transaction": undefined, + "approval_transaction": undefined, + "chain_id_destination": "eip155:42161", + "chain_id_source": "eip155:42161", + "custom_slippage": true, + "destination_transaction": "PENDING", + "error_message": "error_message", + "gas_included": false, + "is_hardware_wallet": false, + "price_impact": 0, + "provider": "lifi_across", + "quote_vs_execution_ratio": 1, + "quoted_time_minutes": 0, + "quoted_vs_used_gas_ratio": 1, + "security_warnings": Array [], + "slippage_limit": 0, + "source_transaction": "COMPLETE", + "stx_enabled": true, + "swap_type": "single_chain", + "token_address_destination": "eip155:10/slip44:60", + "token_address_source": "eip155:42161/slip44:60", + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_actual_gas": 0, + "usd_actual_return": 0, + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with approval 1`] = ` +Object { + "chainId": "0xa4b1", + "hash": "0xevmTxHash", + "id": "test-tx-id", + "status": "unapproved", + "time": 1234567890, + "txParams": Object { + "chainId": "0xa4b1", + "data": "0xdata", + "from": "0xaccount1", + "gasLimit": "0x5208", + "to": "0xbridgeContract", + "value": "0x0", + }, + "type": "swap", +} +`; + +exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with approval 2`] = ` +Object { + "bridge": "across", + "bridgeId": "lifi", + "destChainId": 42161, + "quote": Object { + "bridgeId": "lifi", + "bridges": Array [ + "across", + ], + "destAsset": Object { + "address": "0x0000000000000000000000000000000000000000", + "assetId": "eip155:10/slip44:60", + "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": 42161, + "destTokenAmount": "990654755978612", + "feeData": Object { + "metabridge": Object { + "amount": "8750000000000", + "asset": Object { + "address": "0x0000000000000000000000000000000000000000", + "assetId": "eip155:42161/slip44:60", + "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", + "assetId": "eip155:42161/slip44:60", + "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", + "assetId": "eip155:10/slip44:60", + "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", + "assetId": "eip155:42161/slip44:60", + "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, + }, + ], + }, + "refuel": false, + "srcChainId": 42161, + "srcTxHash": "0xevmTxHash", +} +`; + +exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with approval 3`] = ` +Array [ + Array [ + Object { + "chainId": "0xa4b1", + "data": "0xapprovalData", + "from": "0xaccount1", + "gas": "0x5208", + "gasLimit": "21000", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, + "to": "0xtokenContract", + "value": "0x0", + }, + Object { + "actionId": "1234567890.456", + "networkClientId": "arbitrum-client-id", + "origin": "metamask", + "requireApproval": false, + "type": "swapApproval", + }, + ], + Array [ + Object { + "chainId": "0xa4b1", + "data": "0xdata", + "from": "0xaccount1", + "gas": "0x5208", + "gasLimit": "21000", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, + "to": "0xbridgeContract", + "value": "0x0", + }, + Object { + "actionId": "1234567890.456", + "networkClientId": "arbitrum", + "origin": "metamask", + "requireApproval": false, + "type": "bridge", + }, + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with approval 4`] = ` +Array [ + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "NetworkController:findNetworkClientIdByChainId", + "0xa4b1", + ], + Array [ + "GasFeeController:getState", + ], + Array [ + "TransactionController:getState", + ], + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "NetworkController:findNetworkClientIdByChainId", + "0xa4b1", + ], + Array [ + "GasFeeController:getState", + ], + Array [ + "TransactionController:getState", + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "action_type": "swapbridge-v1", + "actual_time_minutes": 0, + "allowance_reset_transaction": undefined, + "approval_transaction": "COMPLETE", + "chain_id_destination": "eip155:42161", + "chain_id_source": "eip155:42161", + "custom_slippage": true, + "destination_transaction": "PENDING", + "error_message": "error_message", + "gas_included": false, + "is_hardware_wallet": false, + "price_impact": 0, + "provider": "lifi_across", + "quote_vs_execution_ratio": 1, + "quoted_time_minutes": 0, + "quoted_vs_used_gas_ratio": 1, + "security_warnings": Array [], + "slippage_limit": 0, + "source_transaction": "COMPLETE", + "stx_enabled": false, + "swap_type": "single_chain", + "token_address_destination": "eip155:10/slip44:60", + "token_address_source": "eip155:42161/slip44:60", + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_actual_gas": 0, + "usd_actual_return": 0, + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with no approval 1`] = ` +Object { + "chainId": "0xa4b1", + "hash": "0xevmTxHash", + "id": "test-tx-id", + "status": "unapproved", + "time": 1234567890, + "txParams": Object { + "chainId": "0xa4b1", + "data": "0xdata", + "from": "0xaccount1", + "gasLimit": "0x5208", + "to": "0xbridgeContract", + "value": "0x0", + }, + "type": "swap", +} +`; + +exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with no approval 2`] = ` +Object { + "bridge": "across", + "bridgeId": "lifi", + "destChainId": 42161, + "quote": Object { + "bridgeId": "lifi", + "bridges": Array [ + "across", + ], + "destAsset": Object { + "address": "0x0000000000000000000000000000000000000032", + "assetId": "eip155:10/slip44:60", + "chainId": 10, + "coinKey": "WETH", + "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": "WETH", + "priceUSD": "2478.63", + "symbol": "WETH", + }, + "destChainId": 42161, + "destTokenAmount": "990654755978612", + "feeData": Object { + "metabridge": Object { + "amount": "8750000000000", + "asset": Object { + "address": "0x0000000000000000000000000000000000000000", + "assetId": "eip155:42161/slip44:60", + "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", + "assetId": "eip155:42161/slip44:60", + "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", + "assetId": "eip155:10/slip44:60", + "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", + "assetId": "eip155:42161/slip44:60", + "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, + }, + ], + }, + "refuel": false, + "srcChainId": 42161, + "srcTxHash": "0xevmTxHash", +} +`; + +exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with no approval 3`] = ` +Array [ + Array [ + Object { + "chainId": "0xa4b1", + "networkClientId": "arbitrum", + "transactionParams": Object { + "chainId": "0xa4b1", + "data": "0xdata", + "from": "0xaccount1", + "gas": "21000", + "gasLimit": "21000", + "to": "0xbridgeContract", + "value": "0x0", + }, + }, + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with no approval 4`] = ` +Array [ + Array [ + Object { + "chainId": "0xa4b1", + "data": "0xdata", + "from": "0xaccount1", + "gas": "0x5208", + "gasLimit": "21000", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, + "to": "0xbridgeContract", + "value": "0x0", + }, + Object { + "actionId": "1234567890.456", + "networkClientId": "arbitrum", + "origin": "metamask", + "requireApproval": false, + "type": "bridge", + }, + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with no approval 5`] = ` +Array [ + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "NetworkController:findNetworkClientIdByChainId", + "0xa4b1", + ], + Array [ + "GasFeeController:getState", + ], + Array [ + "TransactionController:getState", + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "action_type": "swapbridge-v1", + "actual_time_minutes": 0, + "allowance_reset_transaction": undefined, + "approval_transaction": undefined, + "chain_id_destination": "eip155:42161", + "chain_id_source": "eip155:42161", + "custom_slippage": true, + "destination_transaction": "PENDING", + "error_message": "error_message", + "gas_included": false, + "is_hardware_wallet": false, + "price_impact": 0, + "provider": "lifi_across", + "quote_vs_execution_ratio": 1, + "quoted_time_minutes": 0, + "quoted_vs_used_gas_ratio": 1, + "security_warnings": Array [], + "slippage_limit": 0, + "source_transaction": "COMPLETE", + "stx_enabled": false, + "swap_type": "single_chain", + "token_address_destination": "eip155:10/slip44:60", + "token_address_source": "eip155:42161/slip44:60", + "token_symbol_destination": "WETH", + "token_symbol_source": "ETH", + "usd_actual_gas": 0, + "usd_actual_return": 0, + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], +] +`; + exports[`BridgeStatusController submitTx: Solana should successfully submit a Solana transaction 1`] = ` Array [ Array [ 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 a57a741c768..97c2e46703b 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -1517,7 +1517,7 @@ describe('BridgeStatusController', () => { }); }); - describe('submitTx: EVM', () => { + describe('submitTx: EVM bridge', () => { const mockEvmQuoteResponse = { ...getMockQuote(), quote: { @@ -1922,6 +1922,264 @@ describe('BridgeStatusController', () => { }); }); + describe('submitTx: EVM swap', () => { + const mockEvmQuoteResponse = { + ...getMockQuote(), + quote: { + ...getMockQuote(), + srcChainId: 42161, + destChainId: 42161, + }, + estimatedProcessingTimeInSeconds: 0, + 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 }, + trade: { + from: '0xaccount1', + to: '0xbridgeContract', + value: '0x0', + data: '0xdata', + chainId: 42161, + gasLimit: 21000, + }, + approval: { + from: '0xaccount1', + to: '0xtokenContract', + value: '0x0', + data: '0xapprovalData', + chainId: 42161, + gasLimit: 21000, + }, + } as QuoteResponse & QuoteMetadata; + + const mockEvmTxMeta = { + id: 'test-tx-id', + hash: '0xevmTxHash', + time: 1234567890, + status: 'unapproved', + type: TransactionType.swap, + chainId: '0xa4b1', // 42161 in hex + txParams: { + from: '0xaccount1', + to: '0xbridgeContract', + value: '0x0', + data: '0xdata', + chainId: '0xa4b1', + gasLimit: '0x5208', + }, + }; + + const mockApprovalTxMeta = { + id: 'test-approval-tx-id', + hash: '0xapprovalTxHash', + time: 1234567890, + status: 'unapproved', + type: TransactionType.swapApproval, + chainId: '0xa4b1', // 42161 in hex + txParams: { + from: '0xaccount1', + to: '0xtokenContract', + value: '0x0', + data: '0xapprovalData', + chainId: '0xa4b1', + gasLimit: '0x5208', + }, + }; + + const mockEstimateGasFeeResult = { + estimates: { + high: { + suggestedMaxFeePerGas: '0x1234', + suggestedMaxPriorityFeePerGas: '0x5678', + }, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + jest.spyOn(Date, 'now').mockReturnValue(1234567890); + jest.spyOn(Math, 'random').mockReturnValue(0.456); + }); + + const setupApprovalMocks = () => { + mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); + mockMessengerCall.mockReturnValueOnce('arbitrum-client-id'); + mockMessengerCall.mockReturnValueOnce({ + gasFeeEstimates: { estimatedBaseFee: '0x1234' }, + }); + estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); + addTransactionFn.mockResolvedValueOnce({ + transactionMeta: mockApprovalTxMeta, + result: Promise.resolve('0xapprovalTxHash'), + }); + mockMessengerCall.mockReturnValueOnce({ + transactions: [mockApprovalTxMeta], + }); + }; + + const setupBridgeMocks = () => { + mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); + mockMessengerCall.mockReturnValueOnce('arbitrum'); + mockMessengerCall.mockReturnValueOnce({ + gasFeeEstimates: { estimatedBaseFee: '0x1234' }, + }); + estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); + addTransactionFn.mockResolvedValueOnce({ + transactionMeta: mockEvmTxMeta, + result: Promise.resolve('0xevmTxHash'), + }); + mockMessengerCall.mockReturnValueOnce({ + transactions: [mockEvmTxMeta], + }); + + mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); + mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); + }; + + it('should successfully submit an EVM swap transaction with approval', async () => { + setupApprovalMocks(); + setupBridgeMocks(); + + const { controller, startPollingForBridgeTxStatusSpy } = + getController(mockMessengerCall); + const result = await controller.submitTx(mockEvmQuoteResponse, false); + controller.stopAllPolling(); + + expect(result).toMatchSnapshot(); + expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + expect( + startPollingForBridgeTxStatusSpy.mock.lastCall[0].statusRequest, + ).toMatchSnapshot(); + expect( + startPollingForBridgeTxStatusSpy.mock.lastCall[0].bridgeTxMeta, + ).toStrictEqual(result); + expect(startPollingForBridgeTxStatusSpy.mock.lastCall[0].startTime).toBe( + 1234567890, + ); + expect(addTransactionFn.mock.calls).toMatchSnapshot(); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); + expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); + }); + + it('should successfully submit an EVM swap transaction with no approval', async () => { + setupBridgeMocks(); + + const { controller, startPollingForBridgeTxStatusSpy } = + getController(mockMessengerCall); + const erc20Token = { + address: '0x0000000000000000000000000000000000000032', + assetId: `eip155:10/slip44:60` as CaipAssetType, + chainId: 10, + symbol: 'WETH', + decimals: 18, + name: 'WETH', + coinKey: 'WETH', + 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 { approval, ...quoteWithoutApproval } = mockEvmQuoteResponse; + const result = await controller.submitTx( + { + ...quoteWithoutApproval, + quote: { ...quoteWithoutApproval.quote, destAsset: erc20Token }, + }, + false, + ); + + expect(result).toMatchSnapshot(); + expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + expect( + startPollingForBridgeTxStatusSpy.mock.lastCall[0].statusRequest, + ).toMatchSnapshot(); + expect( + startPollingForBridgeTxStatusSpy.mock.lastCall[0].bridgeTxMeta, + ).toStrictEqual(result); + expect(startPollingForBridgeTxStatusSpy.mock.lastCall[0].startTime).toBe( + 1234567890, + ); + expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); + expect(addTransactionFn.mock.calls).toMatchSnapshot(); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); + expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); + }); + + it('should handle smart transactions', async () => { + setupBridgeMocks(); + + const { controller, startPollingForBridgeTxStatusSpy } = + getController(mockMessengerCall); + const { approval, ...quoteWithoutApproval } = mockEvmQuoteResponse; + const result = await controller.submitTx(quoteWithoutApproval, true); + controller.stopAllPolling(); + + expect(result).toMatchSnapshot(); + expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + expect( + startPollingForBridgeTxStatusSpy.mock.lastCall[0].statusRequest, + ).toMatchSnapshot(); + expect( + startPollingForBridgeTxStatusSpy.mock.lastCall[0].bridgeTxMeta, + ).toStrictEqual(result); + expect(startPollingForBridgeTxStatusSpy.mock.lastCall[0].startTime).toBe( + 1234567890, + ); + expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); + expect(addTransactionFn.mock.calls).toMatchSnapshot(); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); + expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); + }); + + it('should handle smart accounts (4337)', async () => { + mockMessengerCall.mockReturnValueOnce({ + ...mockSelectedAccount, + type: EthAccountType.Erc4337, + }); + mockMessengerCall.mockReturnValueOnce('arbitrum'); + estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); + mockMessengerCall.mockReturnValueOnce({ + gasFeeEstimates: { estimatedBaseFee: '0x1234' }, + }); + addUserOperationFromTransactionFn.mockResolvedValueOnce({ + id: 'user-op-id', + transactionHash: Promise.resolve('0xevmTxHash'), + hash: Promise.resolve('0xevmTxHash'), + }); + mockMessengerCall.mockReturnValueOnce({ + transactions: [mockEvmTxMeta], + }); + estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); + + const { controller, startPollingForBridgeTxStatusSpy } = + getController(mockMessengerCall); + const { approval, ...quoteWithoutApproval } = mockEvmQuoteResponse; + const result = await controller.submitTx(quoteWithoutApproval, false); + controller.stopAllPolling(); + + expect(result).toMatchSnapshot(); + expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + expect( + startPollingForBridgeTxStatusSpy.mock.lastCall[0].statusRequest, + ).toMatchSnapshot(); + expect( + startPollingForBridgeTxStatusSpy.mock.lastCall[0].bridgeTxMeta, + ).toStrictEqual(result); + expect(startPollingForBridgeTxStatusSpy.mock.lastCall[0].startTime).toBe( + 1234567890, + ); + expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); + expect(addTransactionFn).not.toHaveBeenCalled(); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); + expect(addUserOperationFromTransactionFn.mock.calls).toMatchSnapshot(); + }); + }); + describe('subscription handlers', () => { let mockBridgeStatusMessenger: jest.Mocked; let mockTrackEventFn: jest.Mock; From c8b835800f9cbc5a14aab763371a866335a87b75 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 20 May 2025 16:15:02 -0700 Subject: [PATCH 19/20] Revert "fix: update balances after bridge tx" This reverts commit d9a0246bc42e30ed9febff11c7e3d8b94a3ff72d. --- packages/transaction-controller/CHANGELOG.md | 1 - packages/transaction-controller/src/TransactionController.ts | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 9272f66403c..aa85eb11397 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -13,7 +13,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Update balances after a bridge transaction ([#5829](https://github.com/MetaMask/core/pull/5829)) - Fix `userFeeLevel` as `dappSuggested` initially when dApp suggested gas values for legacy transactions ([#5821](https://github.com/MetaMask/core/pull/5821)) - Fix `addTransaction` function to correctly identify a transaction as a `simpleSend` type when the recipient is a smart account ([#5822](https://github.com/MetaMask/core/pull/5822)) diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index f3a99a5cc71..beb67f49614 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -3672,10 +3672,7 @@ export class TransactionController extends BaseController< try { const { networkClientId, type } = transactionMeta; - if ( - type && - ![TransactionType.swap, TransactionType.bridge].includes(type) - ) { + if (type !== TransactionType.swap) { return; } From e7d824bf8539b694bb02d5cc2f81f3e2be9cb14e Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 20 May 2025 16:22:53 -0700 Subject: [PATCH 20/20] fix: changelog --- packages/bridge-status-controller/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index f73538e7ad2..97eda4b7238 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Remove the published bridgeTransactionComplete and bridgeTransactionFailed events ([#5829](https://github.com/MetaMask/core/pull/5829)) - Modify events to use `swap` and `swapApproval` TransactionTypes when src and dest chain are the same ([#5829](https://github.com/MetaMask/core/pull/5829)) -- Bump `@metamask/bridge-controller` peer dependency to `^25.0.1` ([#5811](https://github.com/MetaMask/core/pull/5811)) +- Bump `@metamask/bridge-controller` dev dependency to `^25.0.1` ([#5811](https://github.com/MetaMask/core/pull/5811)) - Bump `@metamask/controller-utils` to `^11.9.0` ([#5812](https://github.com/MetaMask/core/pull/5812)) ### Fixed