From c70209d3c893d3d212419d1a9afff902dc2fc21c Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:53:31 -0400 Subject: [PATCH 1/9] fix: ledger txs for swap and bridge --- .../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 13bc8a6a59..686a4139d6 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -1086,6 +1086,14 @@ export class BridgeStatusController extends StaticIntervalPollingController + setTimeout(resolve, 1000), + ); + await mobileHardwareWalletDelay; + } + return await this.#handleEvmTransaction({ transactionType: isBridgeTx ? TransactionType.bridge From ede2edb14c967ae7aaa4efae7549ca64ecb3f4ae Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 13 Aug 2025 10:53:09 -0400 Subject: [PATCH 2/9] chore: changelog --- packages/bridge-status-controller/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 953cb6ac58..595e11da6a 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Wait for Mobile hardware wallet delay before submitting Ledger tx ([#6302](https://github.com/MetaMask/core/pull/6302)) + ## [38.0.0] ### Added From 3c736954affe5ca46ed617748461441baff0bbd7 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:17:03 -0400 Subject: [PATCH 3/9] chore: clean up code --- .../src/bridge-status-controller.ts | 8 ++------ .../src/utils/transaction.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 686a4139d6..8909316d53 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -69,6 +69,7 @@ import { getStatusRequestParams, getUSDTAllowanceResetTx, handleLineaDelay, + handleMobileHardwareWalletDelay, handleSolanaTxResponse, } from './utils/transaction'; import { generateActionId } from './utils/transaction'; @@ -1087,12 +1088,7 @@ export class BridgeStatusController extends StaticIntervalPollingController - setTimeout(resolve, 1000), - ); - await mobileHardwareWalletDelay; - } + await handleMobileHardwareWalletDelay(requireApproval); return await this.#handleEvmTransaction({ transactionType: isBridgeTx diff --git a/packages/bridge-status-controller/src/utils/transaction.ts b/packages/bridge-status-controller/src/utils/transaction.ts index ef35c0a311..6a11e067ad 100644 --- a/packages/bridge-status-controller/src/utils/transaction.ts +++ b/packages/bridge-status-controller/src/utils/transaction.ts @@ -177,6 +177,24 @@ export const handleLineaDelay = async ( } }; +/** + * Adds a delay for hardware wallet transactions on mobile to fix an issue + * where the Ledger does not get prompted for the 2nd approval. + * Extension does not have this issue. + * + * @param requireApproval - Whether the delay should be applied + */ +export const handleMobileHardwareWalletDelay = async ( + requireApproval: boolean, +) => { + if (requireApproval) { + const mobileHardwareWalletDelay = new Promise((resolve) => + setTimeout(resolve, 1000), + ); + await mobileHardwareWalletDelay; + } +}; + export const getClientRequest = ( quoteResponse: Omit, 'approval'> & QuoteMetadata, selectedAccount: AccountsControllerState['internalAccounts']['accounts'][string], From 91fb6df965b08a597e11ce9c5b66df1d67f9c6b1 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:17:07 -0400 Subject: [PATCH 4/9] chore: tests --- .../src/bridge-status-controller.test.ts | 194 ++++++++++++++++++ .../src/utils/transaction.test.ts | 43 ++++ 2 files changed, 237 insertions(+) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index 05a940ca0f..dba7054b6e 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -2232,6 +2232,200 @@ describe('BridgeStatusController', () => { expect(mockMessengerCall.mock.calls).toMatchSnapshot(); expect(mockTraceFn.mock.calls).toMatchSnapshot(); }); + + it('should call handleMobileHardwareWalletDelay for hardware wallet on mobile', async () => { + // Mock the delay utility function + const handleMobileHardwareWalletDelaySpy = jest + .spyOn(transactionUtils, 'handleMobileHardwareWalletDelay') + .mockResolvedValueOnce(); + + // Setup mocks + setupApprovalMocks(); + setupBridgeMocks(); + + // Mock a hardware wallet account + const hardwareWalletAccount = { + ...mockSelectedAccount, + metadata: { + ...mockSelectedAccount.metadata, + keyring: { + type: 'Ledger Hardware', + }, + }, + }; + + // Override the account mock to return hardware wallet + mockMessengerCall.mockReset(); + mockMessengerCall.mockImplementationOnce(jest.fn()); // stopPollingForQuotes + mockMessengerCall.mockImplementationOnce(jest.fn()); // track event + + // Setup approval mocks with hardware wallet account + mockMessengerCall.mockReturnValueOnce(hardwareWalletAccount); // for approval + 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], + }); + + // Setup bridge mocks with hardware wallet account + mockMessengerCall.mockReturnValueOnce(hardwareWalletAccount); // for bridge tx + 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(hardwareWalletAccount); // for tracking + + // Create controller with MOBILE client ID + const controller = new BridgeStatusController({ + messenger: { + call: mockMessengerCall, + subscribe: mockMessengerSubscribe, + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as never, + clientId: BridgeClientId.MOBILE, // Use MOBILE client + fetchFn: jest.fn(), + addTransactionFn, + addTransactionBatchFn, + updateTransactionFn, + estimateGasFeeFn, + }); + + const result = await controller.submitTx(mockEvmQuoteResponse, false); + controller.stopAllPolling(); + + // Verify that the delay utility was called with true (requireApproval = true) + expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledTimes(1); + expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledWith(true); + expect(result).toBeDefined(); + expect(result.id).toBe('test-tx-id'); + expect(addTransactionFn).toHaveBeenCalledTimes(2); // approval + bridge tx + }); + + it('should not call handleMobileHardwareWalletDelay for hardware wallet on extension', async () => { + // Mock the delay utility function + const handleMobileHardwareWalletDelaySpy = jest + .spyOn(transactionUtils, 'handleMobileHardwareWalletDelay') + .mockResolvedValueOnce(); + + // Setup mocks + setupApprovalMocks(); + setupBridgeMocks(); + + // Mock a hardware wallet account + const hardwareWalletAccount = { + ...mockSelectedAccount, + metadata: { + ...mockSelectedAccount.metadata, + keyring: { + type: 'Ledger Hardware', + }, + }, + }; + + // Override the account mock to return hardware wallet + mockMessengerCall.mockReset(); + mockMessengerCall.mockImplementationOnce(jest.fn()); // stopPollingForQuotes + mockMessengerCall.mockImplementationOnce(jest.fn()); // track event + + // Setup approval mocks with hardware wallet account + mockMessengerCall.mockReturnValueOnce(hardwareWalletAccount); // for approval + 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], + }); + + // Setup bridge mocks with hardware wallet account + mockMessengerCall.mockReturnValueOnce(hardwareWalletAccount); // for bridge tx + 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(hardwareWalletAccount); // for tracking + + // Create controller with EXTENSION client ID (default) + const { controller } = getController(mockMessengerCall); + + const result = await controller.submitTx(mockEvmQuoteResponse, false); + controller.stopAllPolling(); + + // Verify that the delay utility was called with false (requireApproval = false for extension) + expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledTimes(1); + expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledWith(false); + expect(result).toBeDefined(); + expect(result.id).toBe('test-tx-id'); + expect(addTransactionFn).toHaveBeenCalledTimes(2); // approval + bridge tx + }); + + it('should not call handleMobileHardwareWalletDelay with true for non-hardware wallet on mobile', async () => { + // Mock the delay utility function + const handleMobileHardwareWalletDelaySpy = jest + .spyOn(transactionUtils, 'handleMobileHardwareWalletDelay') + .mockResolvedValueOnce(); + + // Setup mocks + setupApprovalMocks(); + setupBridgeMocks(); + + // Create controller with MOBILE client ID but regular account + const controller = new BridgeStatusController({ + messenger: { + call: mockMessengerCall, + subscribe: mockMessengerSubscribe, + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as never, + clientId: BridgeClientId.MOBILE, // Use MOBILE client + fetchFn: jest.fn(), + addTransactionFn, + addTransactionBatchFn, + updateTransactionFn, + estimateGasFeeFn, + }); + + const result = await controller.submitTx(mockEvmQuoteResponse, false); + controller.stopAllPolling(); + + // Verify that the delay utility was called with false (requireApproval = false for non-hardware wallet) + expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledTimes(1); + expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledWith(false); + expect(result).toBeDefined(); + expect(result.id).toBe('test-tx-id'); + expect(addTransactionFn).toHaveBeenCalledTimes(2); // approval + bridge tx + }); }); describe('submitTx: EVM swap', () => { diff --git a/packages/bridge-status-controller/src/utils/transaction.test.ts b/packages/bridge-status-controller/src/utils/transaction.test.ts index d1e25e130c..5d815cb5b4 100644 --- a/packages/bridge-status-controller/src/utils/transaction.test.ts +++ b/packages/bridge-status-controller/src/utils/transaction.test.ts @@ -17,6 +17,7 @@ import { getTxMetaFields, handleSolanaTxResponse, handleLineaDelay, + handleMobileHardwareWalletDelay, getClientRequest, toBatchTxParams, } from './transaction'; @@ -1001,6 +1002,48 @@ describe('Bridge Status Controller Transaction Utils', () => { }); }); + describe('handleMobileHardwareWalletDelay', () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should delay when requireApproval is true', async () => { + // Create a promise that will resolve after the delay + const delayPromise = handleMobileHardwareWalletDelay(true); + + // Verify that the timer was set with the correct delay (1000ms) + expect(jest.getTimerCount()).toBe(1); + + // Fast-forward the timer by 1000ms + jest.advanceTimersByTime(1000); + + // Wait for the promise to resolve + await delayPromise; + + // Verify that the timer was cleared + expect(jest.getTimerCount()).toBe(0); + }); + + it('should not delay when requireApproval is false', async () => { + // Create a promise that will resolve without delay + const delayPromise = handleMobileHardwareWalletDelay(false); + + // Verify that no timer was set + expect(jest.getTimerCount()).toBe(0); + + // Wait for the promise to resolve + await delayPromise; + + // Verify that no timer was set + expect(jest.getTimerCount()).toBe(0); + }); + }); + describe('getClientRequest', () => { it('should generate a valid client request', () => { const mockQuoteResponse: Omit, 'approval'> & From cad03623cbfdcc18a67c994ff6643af0e9b5283c Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:43:54 -0400 Subject: [PATCH 5/9] fix: tests --- .../bridge-status-controller.test.ts.snap | 221 ++++++++++++++++++ .../src/bridge-status-controller.test.ts | 198 +++------------- 2 files changed, 249 insertions(+), 170 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 cc54c35c71..e1ca79f471 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 @@ -349,6 +349,227 @@ Array [ ] `; +exports[`BridgeStatusController submitTx: EVM bridge should call handleMobileHardwareWalletDelay for hardware wallet on mobile 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": "bridge", +} +`; + +exports[`BridgeStatusController submitTx: EVM bridge should call handleMobileHardwareWalletDelay for hardware wallet on mobile 2`] = ` +Object { + "account": "0xaccount1", + "approvalTxId": "test-approval-tx-id", + "batchId": undefined, + "estimatedProcessingTimeInSeconds": 15, + "hasApprovalTx": true, + "initialDestAssetBalance": undefined, + "isStxEnabled": false, + "pricingData": Object { + "amountSent": "1.234", + "amountSentInUsd": "1.01", + "quotedGasInUsd": undefined, + "quotedReturnInUsd": "0.134214", + }, + "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": 10, + "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, + }, + ], + }, + "slippagePercentage": 0, + "startTime": 1234567890, + "status": Object { + "srcChain": Object { + "chainId": 42161, + "txHash": "0xevmTxHash", + }, + "status": "PENDING", + }, + "targetContractAddress": undefined, + "txMetaId": "test-tx-id", +} +`; + +exports[`BridgeStatusController submitTx: EVM bridge should call handleMobileHardwareWalletDelay for hardware wallet on mobile 3`] = ` +Array [ + Array [ + "BridgeController:stopPollingForQuotes", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "gas_included": false, + "price_impact": 0, + "provider": "lifi_across", + "quoted_time_minutes": 0.25, + "stx_enabled": false, + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_amount_source": 1.01, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], + 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", + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM bridge should call handleMobileHardwareWalletDelay for hardware wallet on mobile 4`] = ` +Array [ + Array [ + Object { + "data": Object { + "srcChainId": "eip155:42161", + "stxEnabled": false, + }, + "name": "Bridge Transaction Completed", + }, + [Function], + ], + Array [ + Object { + "data": Object { + "srcChainId": "eip155:42161", + "stxEnabled": false, + }, + "name": "Bridge Transaction Approval Completed", + }, + [Function], + ], +] +`; + exports[`BridgeStatusController submitTx: EVM bridge should delay after submitting linea approval 1`] = ` Object { "chainId": "0xa4b1", 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 dba7054b6e..36019f4534 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -594,7 +594,11 @@ const addTransactionBatchFn = jest.fn(); const updateTransactionFn = jest.fn(); const estimateGasFeeFn = jest.fn(); -const getController = (call: jest.Mock, traceFn?: jest.Mock) => { +const getController = ( + call: jest.Mock, + traceFn?: jest.Mock, + clientId: BridgeClientId = BridgeClientId.EXTENSION, +) => { const controller = new BridgeStatusController({ messenger: { call, @@ -603,7 +607,7 @@ const getController = (call: jest.Mock, traceFn?: jest.Mock) => { registerActionHandler: jest.fn(), registerInitialEventPayload: jest.fn(), } as never, - clientId: BridgeClientId.EXTENSION, + clientId, fetchFn: mockFetchFn, addTransactionFn, addTransactionBatchFn, @@ -2234,17 +2238,15 @@ describe('BridgeStatusController', () => { }); it('should call handleMobileHardwareWalletDelay for hardware wallet on mobile', async () => { - // Mock the delay utility function const handleMobileHardwareWalletDelaySpy = jest .spyOn(transactionUtils, 'handleMobileHardwareWalletDelay') .mockResolvedValueOnce(); + const mockTraceFn = jest + .fn() + .mockImplementation((_p, callback) => callback()); - // Setup mocks - setupApprovalMocks(); - setupBridgeMocks(); - - // Mock a hardware wallet account - const hardwareWalletAccount = { + // Mock for hardware wallet check + mockMessengerCall.mockReturnValueOnce({ ...mockSelectedAccount, metadata: { ...mockSelectedAccount.metadata, @@ -2252,180 +2254,36 @@ describe('BridgeStatusController', () => { type: 'Ledger Hardware', }, }, - }; - - // Override the account mock to return hardware wallet - mockMessengerCall.mockReset(); - mockMessengerCall.mockImplementationOnce(jest.fn()); // stopPollingForQuotes - mockMessengerCall.mockImplementationOnce(jest.fn()); // track event - - // Setup approval mocks with hardware wallet account - mockMessengerCall.mockReturnValueOnce(hardwareWalletAccount); // for approval - 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], }); - // Setup bridge mocks with hardware wallet account - mockMessengerCall.mockReturnValueOnce(hardwareWalletAccount); // for bridge tx - 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(hardwareWalletAccount); // for tracking - - // Create controller with MOBILE client ID - const controller = new BridgeStatusController({ - messenger: { - call: mockMessengerCall, - subscribe: mockMessengerSubscribe, - publish: jest.fn(), - registerActionHandler: jest.fn(), - registerInitialEventPayload: jest.fn(), - } as never, - clientId: BridgeClientId.MOBILE, // Use MOBILE client - fetchFn: jest.fn(), - addTransactionFn, - addTransactionBatchFn, - updateTransactionFn, - estimateGasFeeFn, - }); - - const result = await controller.submitTx(mockEvmQuoteResponse, false); - controller.stopAllPolling(); - - // Verify that the delay utility was called with true (requireApproval = true) - expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledTimes(1); - expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledWith(true); - expect(result).toBeDefined(); - expect(result.id).toBe('test-tx-id'); - expect(addTransactionFn).toHaveBeenCalledTimes(2); // approval + bridge tx - }); - - it('should not call handleMobileHardwareWalletDelay for hardware wallet on extension', async () => { - // Mock the delay utility function - const handleMobileHardwareWalletDelaySpy = jest - .spyOn(transactionUtils, 'handleMobileHardwareWalletDelay') - .mockResolvedValueOnce(); - - // Setup mocks setupApprovalMocks(); setupBridgeMocks(); - // Mock a hardware wallet account - const hardwareWalletAccount = { - ...mockSelectedAccount, - metadata: { - ...mockSelectedAccount.metadata, - keyring: { - type: 'Ledger Hardware', - }, - }, - }; - - // Override the account mock to return hardware wallet - mockMessengerCall.mockReset(); - mockMessengerCall.mockImplementationOnce(jest.fn()); // stopPollingForQuotes - mockMessengerCall.mockImplementationOnce(jest.fn()); // track event - - // Setup approval mocks with hardware wallet account - mockMessengerCall.mockReturnValueOnce(hardwareWalletAccount); // for approval - 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], - }); - - // Setup bridge mocks with hardware wallet account - mockMessengerCall.mockReturnValueOnce(hardwareWalletAccount); // for bridge tx - 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(hardwareWalletAccount); // for tracking - - // Create controller with EXTENSION client ID (default) - const { controller } = getController(mockMessengerCall); + const { controller, startPollingForBridgeTxStatusSpy } = getController( + mockMessengerCall, + mockTraceFn, + BridgeClientId.MOBILE, + ); const result = await controller.submitTx(mockEvmQuoteResponse, false); controller.stopAllPolling(); - // Verify that the delay utility was called with false (requireApproval = false for extension) + expect(mockTraceFn).toHaveBeenCalledTimes(2); expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledTimes(1); - expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledWith(false); - expect(result).toBeDefined(); - expect(result.id).toBe('test-tx-id'); - expect(addTransactionFn).toHaveBeenCalledTimes(2); // approval + bridge tx + expect(result).toMatchSnapshot(); + expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); + expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); + expect(mockTraceFn.mock.calls).toMatchSnapshot(); }); - it('should not call handleMobileHardwareWalletDelay with true for non-hardware wallet on mobile', async () => { - // Mock the delay utility function - const handleMobileHardwareWalletDelaySpy = jest - .spyOn(transactionUtils, 'handleMobileHardwareWalletDelay') - .mockResolvedValueOnce(); - - // Setup mocks - setupApprovalMocks(); - setupBridgeMocks(); - - // Create controller with MOBILE client ID but regular account - const controller = new BridgeStatusController({ - messenger: { - call: mockMessengerCall, - subscribe: mockMessengerSubscribe, - publish: jest.fn(), - registerActionHandler: jest.fn(), - registerInitialEventPayload: jest.fn(), - } as never, - clientId: BridgeClientId.MOBILE, // Use MOBILE client - fetchFn: jest.fn(), - addTransactionFn, - addTransactionBatchFn, - updateTransactionFn, - estimateGasFeeFn, - }); - - const result = await controller.submitTx(mockEvmQuoteResponse, false); - controller.stopAllPolling(); + it.todo( + 'should not call handleMobileHardwareWalletDelay for hardware wallet on extension', + ); - // Verify that the delay utility was called with false (requireApproval = false for non-hardware wallet) - expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledTimes(1); - expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledWith(false); - expect(result).toBeDefined(); - expect(result.id).toBe('test-tx-id'); - expect(addTransactionFn).toHaveBeenCalledTimes(2); // approval + bridge tx - }); + it.todo( + 'should not call handleMobileHardwareWalletDelay with true for non-hardware wallet on mobile', + ); }); describe('submitTx: EVM swap', () => { From 713c4015db9abcbe7931dfb9eeaf33b42dcc124d Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:48:11 -0400 Subject: [PATCH 6/9] chore: tests --- .../bridge-status-controller.test.ts.snap | 439 ++++++++++++++++++ .../src/bridge-status-controller.test.ts | 79 +++- 2 files changed, 512 insertions(+), 6 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 e1ca79f471..2c46c18d60 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 @@ -1018,6 +1018,445 @@ Array [ ] `; +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay for hardware wallet on extension 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": "bridge", +} +`; + +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay for hardware wallet on extension 2`] = ` +Object { + "account": "0xaccount1", + "approvalTxId": "test-approval-tx-id", + "batchId": undefined, + "estimatedProcessingTimeInSeconds": 15, + "hasApprovalTx": true, + "initialDestAssetBalance": undefined, + "isStxEnabled": false, + "pricingData": Object { + "amountSent": "1.234", + "amountSentInUsd": "1.01", + "quotedGasInUsd": undefined, + "quotedReturnInUsd": "0.134214", + }, + "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": 10, + "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, + }, + ], + }, + "slippagePercentage": 0, + "startTime": 1234567890, + "status": Object { + "srcChain": Object { + "chainId": 42161, + "txHash": "0xevmTxHash", + }, + "status": "PENDING", + }, + "targetContractAddress": undefined, + "txMetaId": "test-tx-id", +} +`; + +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay for hardware wallet on extension 3`] = ` +Array [ + Array [ + "BridgeController:stopPollingForQuotes", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "gas_included": false, + "price_impact": 0, + "provider": "lifi_across", + "quoted_time_minutes": 0.25, + "stx_enabled": false, + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_amount_source": 1.01, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], + 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", + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay for hardware wallet on extension 4`] = ` +Array [ + Array [ + Object { + "data": Object { + "srcChainId": "eip155:42161", + "stxEnabled": false, + }, + "name": "Bridge Transaction Completed", + }, + [Function], + ], + Array [ + Object { + "data": Object { + "srcChainId": "eip155:42161", + "stxEnabled": false, + }, + "name": "Bridge Transaction Approval Completed", + }, + [Function], + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay with true for non-hardware wallet on mobile 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": "bridge", +} +`; + +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay with true for non-hardware wallet on mobile 2`] = ` +Object { + "account": "0xaccount1", + "approvalTxId": "test-approval-tx-id", + "batchId": undefined, + "estimatedProcessingTimeInSeconds": 15, + "hasApprovalTx": true, + "initialDestAssetBalance": undefined, + "isStxEnabled": false, + "pricingData": Object { + "amountSent": "1.234", + "amountSentInUsd": "1.01", + "quotedGasInUsd": undefined, + "quotedReturnInUsd": "0.134214", + }, + "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": 10, + "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, + }, + ], + }, + "slippagePercentage": 0, + "startTime": 1234567890, + "status": Object { + "srcChain": Object { + "chainId": 42161, + "txHash": "0xevmTxHash", + }, + "status": "PENDING", + }, + "targetContractAddress": undefined, + "txMetaId": "test-tx-id", +} +`; + +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay with true for non-hardware wallet on mobile 3`] = ` +Array [ + Array [ + "BridgeController:stopPollingForQuotes", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "gas_included": false, + "price_impact": 0, + "provider": "lifi_across", + "quoted_time_minutes": 0.25, + "stx_enabled": false, + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_amount_source": 1.01, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], + 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", + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay with true for non-hardware wallet on mobile 4`] = ` +Array [ + Array [ + Object { + "data": Object { + "srcChainId": "eip155:42161", + "stxEnabled": false, + }, + "name": "Bridge Transaction Completed", + }, + [Function], + ], + Array [ + Object { + "data": Object { + "srcChainId": "eip155:42161", + "stxEnabled": false, + }, + "name": "Bridge Transaction Approval Completed", + }, + [Function], + ], +] +`; + exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance 1`] = ` Object { "chainId": "0xa4b1", 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 36019f4534..3958e036fe 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -2277,13 +2277,80 @@ describe('BridgeStatusController', () => { expect(mockTraceFn.mock.calls).toMatchSnapshot(); }); - it.todo( - 'should not call handleMobileHardwareWalletDelay for hardware wallet on extension', - ); + it('should not call handleMobileHardwareWalletDelay for hardware wallet on extension', async () => { + const handleMobileHardwareWalletDelaySpy = jest + .spyOn(transactionUtils, 'handleMobileHardwareWalletDelay') + .mockResolvedValueOnce(); + const mockTraceFn = jest + .fn() + .mockImplementation((_p, callback) => callback()); + + // Mock for hardware wallet check - but this shouldn't be called on extension + // since the check only happens on mobile + setupApprovalMocks(); + setupBridgeMocks(); + + const { controller, startPollingForBridgeTxStatusSpy } = getController( + mockMessengerCall, + mockTraceFn, + BridgeClientId.EXTENSION, // Using EXTENSION client + ); - it.todo( - 'should not call handleMobileHardwareWalletDelay with true for non-hardware wallet on mobile', - ); + const result = await controller.submitTx(mockEvmQuoteResponse, false); + controller.stopAllPolling(); + + expect(mockTraceFn).toHaveBeenCalledTimes(2); + // Should NOT call the mobile hardware wallet delay on extension + expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledTimes(1); + expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledWith(false); + expect(result).toMatchSnapshot(); + expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); + expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); + expect(mockTraceFn.mock.calls).toMatchSnapshot(); + }); + + it('should not call handleMobileHardwareWalletDelay with true for non-hardware wallet on mobile', async () => { + const handleMobileHardwareWalletDelaySpy = jest + .spyOn(transactionUtils, 'handleMobileHardwareWalletDelay') + .mockResolvedValueOnce(); + const mockTraceFn = jest + .fn() + .mockImplementation((_p, callback) => callback()); + + // Mock for non-hardware wallet check + mockMessengerCall.mockReturnValueOnce({ + ...mockSelectedAccount, + metadata: { + ...mockSelectedAccount.metadata, + keyring: { + type: 'HD Key Tree', // Not a hardware wallet + }, + }, + }); + + setupApprovalMocks(); + setupBridgeMocks(); + + const { controller, startPollingForBridgeTxStatusSpy } = getController( + mockMessengerCall, + mockTraceFn, + BridgeClientId.MOBILE, // Using MOBILE client + ); + + const result = await controller.submitTx(mockEvmQuoteResponse, false); + controller.stopAllPolling(); + + expect(mockTraceFn).toHaveBeenCalledTimes(2); + // Should call the function but with false since it's not a hardware wallet + expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledTimes(1); + expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledWith(false); + expect(result).toMatchSnapshot(); + expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); + expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); + expect(mockTraceFn.mock.calls).toMatchSnapshot(); + }); }); describe('submitTx: EVM swap', () => { From cf86fdf48fd401b009f8dfb136eb73114fb8d0a9 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 13 Aug 2025 12:03:11 -0400 Subject: [PATCH 7/9] chore: tests --- .../bridge-status-controller.test.ts.snap | 218 ++++++++++++++++++ .../src/bridge-status-controller.test.ts | 7 +- 2 files changed, 221 insertions(+), 4 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 2c46c18d60..6b51b9108f 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 @@ -1236,6 +1236,224 @@ Array [ ] `; +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay on extension 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": "bridge", +} +`; + +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay on extension 2`] = ` +Object { + "account": "0xaccount1", + "approvalTxId": "test-approval-tx-id", + "batchId": undefined, + "estimatedProcessingTimeInSeconds": 15, + "hasApprovalTx": true, + "initialDestAssetBalance": undefined, + "isStxEnabled": false, + "pricingData": Object { + "amountSent": "1.234", + "amountSentInUsd": "1.01", + "quotedGasInUsd": undefined, + "quotedReturnInUsd": "0.134214", + }, + "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": 10, + "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, + }, + ], + }, + "slippagePercentage": 0, + "startTime": 1234567890, + "status": Object { + "srcChain": Object { + "chainId": 42161, + "txHash": "0xevmTxHash", + }, + "status": "PENDING", + }, + "targetContractAddress": undefined, + "txMetaId": "test-tx-id", +} +`; + +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay on extension 3`] = ` +Array [ + Array [ + "BridgeController:stopPollingForQuotes", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "gas_included": false, + "price_impact": 0, + "provider": "lifi_across", + "quoted_time_minutes": 0.25, + "stx_enabled": false, + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_amount_source": 1.01, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], + 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", + ], +] +`; + +exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay on extension 4`] = ` +Array [ + Array [ + Object { + "data": Object { + "srcChainId": "eip155:42161", + "stxEnabled": false, + }, + "name": "Bridge Transaction Completed", + }, + [Function], + ], + Array [ + Object { + "data": Object { + "srcChainId": "eip155:42161", + "stxEnabled": false, + }, + "name": "Bridge Transaction Approval Completed", + }, + [Function], + ], +] +`; + exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay with true for non-hardware wallet on mobile 1`] = ` Object { "chainId": "0xa4b1", 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 3958e036fe..e797c9c533 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -2270,6 +2270,7 @@ describe('BridgeStatusController', () => { expect(mockTraceFn).toHaveBeenCalledTimes(2); expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledTimes(1); + expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledWith(true); expect(result).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); expect(controller.state.txHistory[result.id]).toMatchSnapshot(); @@ -2277,7 +2278,7 @@ describe('BridgeStatusController', () => { expect(mockTraceFn.mock.calls).toMatchSnapshot(); }); - it('should not call handleMobileHardwareWalletDelay for hardware wallet on extension', async () => { + it('should not call handleMobileHardwareWalletDelay on extension', async () => { const handleMobileHardwareWalletDelaySpy = jest .spyOn(transactionUtils, 'handleMobileHardwareWalletDelay') .mockResolvedValueOnce(); @@ -2285,8 +2286,6 @@ describe('BridgeStatusController', () => { .fn() .mockImplementation((_p, callback) => callback()); - // Mock for hardware wallet check - but this shouldn't be called on extension - // since the check only happens on mobile setupApprovalMocks(); setupBridgeMocks(); @@ -2300,7 +2299,7 @@ describe('BridgeStatusController', () => { controller.stopAllPolling(); expect(mockTraceFn).toHaveBeenCalledTimes(2); - // Should NOT call the mobile hardware wallet delay on extension + // Should call the function but with false since it's Extension expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledTimes(1); expect(handleMobileHardwareWalletDelaySpy).toHaveBeenCalledWith(false); expect(result).toMatchSnapshot(); From e8e9afbdba80edf0ea178045ae851a2eefd83569 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 13 Aug 2025 12:06:01 -0400 Subject: [PATCH 8/9] fix: snapshots --- .../bridge-status-controller.test.ts.snap | 218 ------------------ 1 file changed, 218 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 6b51b9108f..695d92f5f5 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 @@ -1018,224 +1018,6 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay for hardware wallet on extension 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": "bridge", -} -`; - -exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay for hardware wallet on extension 2`] = ` -Object { - "account": "0xaccount1", - "approvalTxId": "test-approval-tx-id", - "batchId": undefined, - "estimatedProcessingTimeInSeconds": 15, - "hasApprovalTx": true, - "initialDestAssetBalance": undefined, - "isStxEnabled": false, - "pricingData": Object { - "amountSent": "1.234", - "amountSentInUsd": "1.01", - "quotedGasInUsd": undefined, - "quotedReturnInUsd": "0.134214", - }, - "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": 10, - "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, - }, - ], - }, - "slippagePercentage": 0, - "startTime": 1234567890, - "status": Object { - "srcChain": Object { - "chainId": 42161, - "txHash": "0xevmTxHash", - }, - "status": "PENDING", - }, - "targetContractAddress": undefined, - "txMetaId": "test-tx-id", -} -`; - -exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay for hardware wallet on extension 3`] = ` -Array [ - Array [ - "BridgeController:stopPollingForQuotes", - ], - Array [ - "BridgeController:trackUnifiedSwapBridgeEvent", - "Unified SwapBridge Submitted", - Object { - "gas_included": false, - "price_impact": 0, - "provider": "lifi_across", - "quoted_time_minutes": 0.25, - "stx_enabled": false, - "token_symbol_destination": "ETH", - "token_symbol_source": "ETH", - "usd_amount_source": 1.01, - "usd_quoted_gas": 0, - "usd_quoted_return": 0, - }, - ], - 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", - ], -] -`; - -exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay for hardware wallet on extension 4`] = ` -Array [ - Array [ - Object { - "data": Object { - "srcChainId": "eip155:42161", - "stxEnabled": false, - }, - "name": "Bridge Transaction Completed", - }, - [Function], - ], - Array [ - Object { - "data": Object { - "srcChainId": "eip155:42161", - "stxEnabled": false, - }, - "name": "Bridge Transaction Approval Completed", - }, - [Function], - ], -] -`; - exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobileHardwareWalletDelay on extension 1`] = ` Object { "chainId": "0xa4b1", From ad23e4390640969909b21ab447c3af56661c7304 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 13 Aug 2025 13:19:15 -0400 Subject: [PATCH 9/9] fix: changelog --- packages/bridge-status-controller/CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 90eabc8cbe..21a59b5a22 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -7,14 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Fixed - -- Wait for Mobile hardware wallet delay before submitting Ledger tx ([#6302](https://github.com/MetaMask/core/pull/6302)) - ### Changed - Bump `@metamask/controller-utils` from `^11.11.0` to `^11.12.0` ([#6303](https://github.com/MetaMask/core/pull/6303)) +### Fixed + +- Wait for Mobile hardware wallet delay before submitting Ledger tx ([#6302](https://github.com/MetaMask/core/pull/6302)) + ## [38.0.0] ### Added