diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 5e1a909a252..a8ac2885c98 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Set correct `can_submit` property on Unified SwapBridge events ([#5993](https://github.com/MetaMask/core/pull/5993)) +- Use activeQuote to populate default properties for Submitted and Failed events, if tx fails before being confirmed on chain ([#5993](https://github.com/MetaMask/core/pull/5993)) + ## [33.0.0] ### Added diff --git a/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap b/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap index 6639f1f0cbd..763623b68f5 100644 --- a/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap +++ b/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap @@ -96,17 +96,22 @@ Array [ "actual_time_minutes": 10, "allowance_reset_transaction": "PENDING", "approval_transaction": "PENDING", + "can_submit": true, "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", "custom_slippage": true, "destination_transaction": "PENDING", "error_message": "error_message", "gas_included": false, + "initial_load_time_all_quotes": 0, "is_hardware_wallet": false, "price_impact": 0, "provider": "provider_bridge", "quoted_time_minutes": 0, + "quotes_count": 0, + "quotes_list": Array [], "security_warnings": Array [], + "slippage_limit": undefined, "source_transaction": "PENDING", "stx_enabled": false, "swap_type": "crosschain", @@ -122,6 +127,43 @@ Array [ ] `; +exports[`BridgeController trackUnifiedSwapBridgeEvent bridge-status-controller calls should track the Failed event before tx is submitted 1`] = ` +Array [ + Array [ + "Unified SwapBridge Failed", + Object { + "action_type": "crosschain-v1", + "can_submit": true, + "chain_id_destination": "eip155:1", + "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "custom_slippage": true, + "error_message": "Failed to submit tx", + "gas_included": false, + "initial_load_time_all_quotes": 0, + "is_hardware_wallet": false, + "price_impact": 12, + "provider": "provider_bridge", + "quoted_time_minutes": 2, + "quotes_count": 2, + "quotes_list": Array [ + "lifi_mayan", + "lifi_mayanMCTP", + ], + "slippage_limit": 0.5, + "stx_enabled": false, + "swap_type": "crosschain", + "token_address_destination": "eip155:1/erc20:0x1234", + "token_address_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:NATIVE", + "token_symbol_destination": "USDC", + "token_symbol_source": "ETH", + "usd_amount_source": 100, + "usd_quoted_gas": 1, + "usd_quoted_return": 113, + }, + ], +] +`; + exports[`BridgeController trackUnifiedSwapBridgeEvent bridge-status-controller calls should track the SnapConfirmationViewed event 1`] = ` Array [ Array [ @@ -153,24 +195,31 @@ Array [ "Unified SwapBridge Submitted", Object { "action_type": "crosschain-v1", + "can_submit": true, "chain_id_destination": "eip155:10", - "chain_id_source": "eip155:1", + "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "custom_slippage": true, "gas_included": false, + "initial_load_time_all_quotes": 0, "is_hardware_wallet": false, - "price_impact": 0, + "price_impact": 12, "provider": "provider_bridge", - "quoted_time_minutes": 0, - "security_warnings": Array [], + "quoted_time_minutes": 2, + "quotes_count": 2, + "quotes_list": Array [ + "lifi_mayan", + "lifi_mayanMCTP", + ], + "slippage_limit": 0.5, "stx_enabled": false, "swap_type": "crosschain", - "token_address_destination": "eip155:10/slip44:60", - "token_address_source": "eip155:1/slip44:60", + "token_address_destination": "eip155:10/erc20:0x1234", + "token_address_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:NATIVE", "token_symbol_destination": "USDC", "token_symbol_source": "ETH", "usd_amount_source": 100, - "usd_quoted_gas": 0, - "usd_quoted_return": 0, + "usd_quoted_gas": 1, + "usd_quoted_return": 113, }, ], ] @@ -182,7 +231,7 @@ Array [ "Unified SwapBridge All Quotes Opened", Object { "action_type": "crosschain-v1", - "can_submit": false, + "can_submit": true, "chain_id_destination": null, "chain_id_source": "eip155:1", "custom_slippage": false, @@ -211,7 +260,7 @@ Array [ Object { "action_type": "crosschain-v1", "best_quote_provider": "provider_bridge2", - "can_submit": false, + "can_submit": true, "chain_id_destination": null, "chain_id_source": "eip155:1", "custom_slippage": false, @@ -295,7 +344,7 @@ Array [ Object { "action_type": "crosschain-v1", "best_quote_provider": "provider_bridge2", - "can_submit": false, + "can_submit": true, "chain_id_destination": null, "chain_id_source": "eip155:1", "custom_slippage": false, @@ -326,7 +375,7 @@ Array [ Object { "action_type": "crosschain-v1", "best_quote_provider": "provider_bridge2", - "can_submit": false, + "can_submit": true, "chain_id_destination": null, "chain_id_source": "eip155:1", "custom_slippage": false, @@ -413,7 +462,7 @@ Array [ Object { "action_type": "crosschain-v1", "best_quote_provider": "provider_bridge2", - "can_submit": true, + "can_submit": false, "chain_id_destination": "eip155:10", "chain_id_source": "eip155:1", "custom_slippage": true, diff --git a/packages/bridge-controller/src/bridge-controller.test.ts b/packages/bridge-controller/src/bridge-controller.test.ts index 3f6af92d5c2..8273a2b5619 100644 --- a/packages/bridge-controller/src/bridge-controller.test.ts +++ b/packages/bridge-controller/src/bridge-controller.test.ts @@ -1761,28 +1761,38 @@ describe('BridgeController', function () { }); it('should track the Submitted event', () => { - bridgeController.trackUnifiedSwapBridgeEvent( + const controller = new BridgeController({ + messenger: messengerMock, + getLayer1GasFee: getLayer1GasFeeMock, + clientId: BridgeClientId.EXTENSION, + fetchFn: mockFetchFn, + trackMetaMetricsFn, + state: { + quoteRequest: { + srcChainId: SolScope.Mainnet, + destChainId: '0xa', + srcTokenAddress: 'NATIVE', + destTokenAddress: '0x1234', + srcTokenAmount: '1000000', + walletAddress: '0x123', + slippage: 0.5, + }, + quotes: mockBridgeQuotesSolErc20 as never, + }, + }); + controller.trackUnifiedSwapBridgeEvent( UnifiedSwapBridgeEventName.Submitted, { - provider: 'provider_bridge', - usd_quoted_gas: 0, + usd_quoted_gas: 1, gas_included: false, - quoted_time_minutes: 0, - usd_quoted_return: 0, - price_impact: 0, - chain_id_source: formatChainIdToCaip(1), + quoted_time_minutes: 2, + usd_quoted_return: 113, + provider: 'provider_bridge', + price_impact: 12, token_symbol_source: 'ETH', - token_address_source: getNativeAssetForChainId(1).assetId, - custom_slippage: true, - usd_amount_source: 100, - stx_enabled: false, - is_hardware_wallet: false, - swap_type: MetricsSwapType.CROSSCHAIN, - action_type: MetricsActionType.CROSSCHAIN_V1, - chain_id_destination: formatChainIdToCaip(10), token_symbol_destination: 'USDC', - token_address_destination: getNativeAssetForChainId(10).assetId, - security_warnings: [], + stx_enabled: false, + usd_amount_source: 100, }, ); expect(trackMetaMetricsFn).toHaveBeenCalledTimes(1); @@ -1864,6 +1874,47 @@ describe('BridgeController', function () { expect(trackMetaMetricsFn.mock.calls).toMatchSnapshot(); }); + + it('should track the Failed event before tx is submitted', () => { + const controller = new BridgeController({ + messenger: messengerMock, + getLayer1GasFee: getLayer1GasFeeMock, + clientId: BridgeClientId.EXTENSION, + fetchFn: mockFetchFn, + trackMetaMetricsFn, + state: { + quoteRequest: { + srcChainId: SolScope.Mainnet, + destChainId: '1', + srcTokenAddress: 'NATIVE', + destTokenAddress: '0x1234', + srcTokenAmount: '1000000', + walletAddress: '0x123', + slippage: 0.5, + }, + quotes: mockBridgeQuotesSolErc20 as never, + }, + }); + controller.trackUnifiedSwapBridgeEvent( + UnifiedSwapBridgeEventName.Failed, + { + error_message: 'Failed to submit tx', + usd_quoted_gas: 1, + gas_included: false, + quoted_time_minutes: 2, + usd_quoted_return: 113, + provider: 'provider_bridge', + price_impact: 12, + token_symbol_source: 'ETH', + token_symbol_destination: 'USDC', + stx_enabled: false, + usd_amount_source: 100, + }, + ); + expect(trackMetaMetricsFn).toHaveBeenCalledTimes(1); + + expect(trackMetaMetricsFn.mock.calls).toMatchSnapshot(); + }); }); describe('trackUnifiedSwapBridgeEvent client-side call exceptions', () => { diff --git a/packages/bridge-controller/src/bridge-controller.ts b/packages/bridge-controller/src/bridge-controller.ts index c8af6a51455..15412820f4a 100644 --- a/packages/bridge-controller/src/bridge-controller.ts +++ b/packages/bridge-controller/src/bridge-controller.ts @@ -760,7 +760,7 @@ export class BridgeController extends StaticIntervalPollingController => { return { - can_submit: Boolean(this.state.quoteRequest.insufficientBal), // TODO check if balance is sufficient for network fees + can_submit: !this.state.quoteRequest.insufficientBal, // TODO check if balance is sufficient for network fees quotes_count: this.state.quotes.length, quotes_list: this.state.quotes.map(({ quote }) => formatProviderLabel(quote), @@ -825,10 +825,19 @@ export class BridgeController extends StaticIntervalPollingController { '1151111081099710': { isActiveSrc: true, isActiveDest: true, + isSnapConfirmationEnabled: false, }, }, }; @@ -79,6 +80,7 @@ describe('feature-flags', () => { 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': { isActiveSrc: true, isActiveDest: true, + isSnapConfirmationEnabled: false, }, }, }); diff --git a/packages/bridge-controller/src/utils/metrics/types.ts b/packages/bridge-controller/src/utils/metrics/types.ts index efff5230165..7b343a6f816 100644 --- a/packages/bridge-controller/src/utils/metrics/types.ts +++ b/packages/bridge-controller/src/utils/metrics/types.ts @@ -122,12 +122,10 @@ export type RequiredEventContextFromClient = { TradeData & { action_type: MetricsActionType; }; - [UnifiedSwapBridgeEventName.Submitted]: RequestParams & - RequestMetadata & + [UnifiedSwapBridgeEventName.Submitted]: TradeData & Pick & - TradeData & { - action_type: MetricsActionType; - }; + Pick & + Pick; [UnifiedSwapBridgeEventName.Completed]: RequestParams & RequestMetadata & TxStatusData & @@ -140,33 +138,40 @@ export type RequiredEventContextFromClient = { quoted_vs_used_gas_ratio: number; action_type: MetricsActionType; }; - [UnifiedSwapBridgeEventName.Failed]: RequestParams & - RequestMetadata & - Pick & - TxStatusData & - TradeData & { - actual_time_minutes: number; - error_message: string; - action_type: MetricsActionType; - }; + [UnifiedSwapBridgeEventName.Failed]: + | // Tx failed before confirmation + (TradeData & + Pick & + Pick & + Pick< + RequestParams, + 'token_symbol_source' | 'token_symbol_destination' + > & { error_message: string }) // Tx failed after confirmation + | (RequestParams & + RequestMetadata & + Pick & + TxStatusData & + TradeData & { + actual_time_minutes: number; + error_message?: string; + action_type: MetricsActionType; + }); // Emitted by clients [UnifiedSwapBridgeEventName.AllQuotesOpened]: Pick< TradeData, 'gas_included' > & - Pick & { + Pick & + Pick & { stx_enabled: RequestMetadata['stx_enabled']; - token_symbol_source: RequestParams['token_symbol_source']; - token_symbol_destination: RequestParams['token_symbol_destination']; }; [UnifiedSwapBridgeEventName.AllQuotesSorted]: Pick< TradeData, 'gas_included' > & - Pick & { + Pick & + Pick & { stx_enabled: RequestMetadata['stx_enabled']; - token_symbol_source: RequestParams['token_symbol_source']; - token_symbol_destination: RequestParams['token_symbol_destination']; sort_order: SortOrder; best_quote_provider: QuoteFetchData['best_quote_provider']; }; @@ -207,9 +212,21 @@ export type EventPropertiesFromControllerState = { RequestParams & QuoteFetchData & TradeData; - [UnifiedSwapBridgeEventName.Submitted]: null; + [UnifiedSwapBridgeEventName.Submitted]: RequestParams & + RequestMetadata & + TradeData & + Pick & { + action_type: MetricsActionType; + }; [UnifiedSwapBridgeEventName.Completed]: null; - [UnifiedSwapBridgeEventName.Failed]: null; + [UnifiedSwapBridgeEventName.Failed]: RequestParams & + RequestMetadata & + TxStatusData & + TradeData & + Pick & { + actual_time_minutes: number; + action_type: MetricsActionType; + }; [UnifiedSwapBridgeEventName.AllQuotesOpened]: RequestParams & RequestMetadata & TradeData & diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 5c154829dcd..84202ab56c0 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Remove `@metamask/multichain-transactions-controller` peer dependency ([#5993](https://github.com/MetaMask/core/pull/5993)) +- **BREAKING:** Adds a call to bridge-controller's `stopPollingForQuotes` handler to prevent quotes from refreshing during tx submission. This enables "pausing" the quote polling loop without resetting the entire state. Without this, it's possible for the activeQuote to change while the UI's tx submission is in-progress ([#5994](https://github.com/MetaMask/core/pull/5994)) - **BREAKING:** BridgeStatusController now requires the `BridgeController:stopPollingForQuotes` action permission ([#5994](https://github.com/MetaMask/core/pull/5994)) - **BREAKING:** Bump peer dependency `@metamask/accounts-controller` to `^31.0.0` ([#5999](https://github.com/MetaMask/core/pull/5999)) - **BREAKING:** Bump peer dependency `@metamask/bridge-controller` to `^33.0.0` ([#5999](https://github.com/MetaMask/core/pull/5999)) @@ -23,6 +25,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Update the following events to match the Unified SwapBridge spec ([#5993](https://github.com/MetaMask/core/pull/5993)) + - `Completed`: remove multichain tx controller subscription and emit the event based on the tx submission status instead + - `Failed`: emit event when an error is thrown during solana tx submission + - `Submitted` + - set swap type for evm txs when applicable. this is currently hardcoded to bridge so swaps don't get displayed correctly on the activity list + - emit this event when submitTx is called, regardless of confirmation status - Parse tx signature from `onClientRequest` response in order to identify bridge transactions ([#6001](https://github.com/MetaMask/core/pull/6001)) - Prevent active quote from changing while transaction submission is in progress ([#5994](https://github.com/MetaMask/core/pull/5994)) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index f6e9115e57c..a79db09dba4 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -62,7 +62,6 @@ "@metamask/auto-changelog": "^3.4.4", "@metamask/bridge-controller": "^33.0.0", "@metamask/gas-fee-controller": "^24.0.0", - "@metamask/multichain-transactions-controller": "^3.0.0", "@metamask/network-controller": "^24.0.0", "@metamask/snaps-controllers": "^12.3.1", "@metamask/transaction-controller": "^58.0.0", @@ -81,7 +80,6 @@ "@metamask/accounts-controller": "^31.0.0", "@metamask/bridge-controller": "^33.0.0", "@metamask/gas-fee-controller": "^24.0.0", - "@metamask/multichain-transactions-controller": "^3.0.0", "@metamask/network-controller": "^24.0.0", "@metamask/snaps-controllers": "^12.0.0", "@metamask/transaction-controller": "^58.0.0" 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 973089b6dee..920f8115c3c 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 @@ -130,10 +130,6 @@ Array [ "TransactionController:transactionConfirmed", [Function], ], - Array [ - "MultichainTransactionsController:transactionConfirmed", - [Function], - ], ] `; @@ -158,7 +154,6 @@ Array [ "chain_id_source": "eip155:42161", "custom_slippage": true, "destination_transaction": "FAILED", - "error_message": "error_message", "gas_included": false, "is_hardware_wallet": false, "price_impact": 0, @@ -326,7 +321,6 @@ Array [ "chain_id_source": "eip155:42161", "custom_slippage": true, "destination_transaction": "COMPLETE", - "error_message": "error_message", "gas_included": false, "is_hardware_wallet": false, "price_impact": 0, @@ -478,6 +472,22 @@ 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": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], Array [ "AccountsController:getAccountByAddress", "0xaccount1", @@ -509,46 +519,6 @@ Array [ Array [ "AccountsController:getSelectedMultichainAccount", ], - Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", - ], - Array [ - "BridgeController:trackUnifiedSwapBridgeEvent", - "Unified SwapBridge Submitted", - Object { - "action_type": "crosschain-v1", - "actual_time_minutes": 0, - "allowance_reset_transaction": undefined, - "approval_transaction": "COMPLETE", - "chain_id_destination": "eip155:10", - "chain_id_source": "eip155:59144", - "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, - }, - ], ] `; @@ -722,6 +692,22 @@ 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": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], Array [ "AccountsController:getAccountByAddress", "0xaccount1", @@ -739,46 +725,6 @@ Array [ Array [ "AccountsController:getSelectedMultichainAccount", ], - Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", - ], - Array [ - "BridgeController:trackUnifiedSwapBridgeEvent", - "Unified SwapBridge Submitted", - 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, - }, - ], ] `; @@ -988,60 +934,36 @@ Array [ Array [ "BridgeController:stopPollingForQuotes", ], - 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": "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": true, - "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, }, ], + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "NetworkController:findNetworkClientIdByChainId", + "0xa4b1", + ], + Array [ + "GasFeeController:getState", + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], ] `; @@ -1285,6 +1207,22 @@ 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": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], Array [ "BridgeController:getBridgeERC20Allowance", "0x0000000000000000000000000000000000000000", @@ -1335,46 +1273,6 @@ Array [ Array [ "AccountsController:getSelectedMultichainAccount", ], - Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", - ], - Array [ - "BridgeController:trackUnifiedSwapBridgeEvent", - "Unified SwapBridge Submitted", - Object { - "action_type": "crosschain-v1", - "actual_time_minutes": 0, - "allowance_reset_transaction": undefined, - "approval_transaction": "COMPLETE", - "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, - }, - ], ] `; @@ -1548,6 +1446,22 @@ 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": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], Array [ "AccountsController:getAccountByAddress", "0xaccount1", @@ -1579,46 +1493,6 @@ Array [ Array [ "AccountsController:getSelectedMultichainAccount", ], - Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", - ], - Array [ - "BridgeController:trackUnifiedSwapBridgeEvent", - "Unified SwapBridge Submitted", - Object { - "action_type": "crosschain-v1", - "actual_time_minutes": 0, - "allowance_reset_transaction": undefined, - "approval_transaction": "COMPLETE", - "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, - }, - ], ] `; @@ -1792,6 +1666,22 @@ 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": "WETH", + "token_symbol_source": "ETH", + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], Array [ "AccountsController:getAccountByAddress", "0xaccount1", @@ -1809,46 +1699,6 @@ Array [ Array [ "AccountsController:getSelectedMultichainAccount", ], - Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", - ], - Array [ - "BridgeController:trackUnifiedSwapBridgeEvent", - "Unified SwapBridge Submitted", - 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": "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, - }, - ], ] `; @@ -1882,6 +1732,22 @@ 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": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], Array [ "AccountsController:getAccountByAddress", "0xaccount1", @@ -1926,6 +1792,22 @@ 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": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], Array [ "AccountsController:getAccountByAddress", "0xaccount1", @@ -2088,6 +1970,22 @@ Array [ Array [ "BridgeController:stopPollingForQuotes", ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "gas_included": false, + "price_impact": 0, + "provider": "lifi_across", + "quoted_time_minutes": 0, + "stx_enabled": false, + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], Array [ "AccountsController:getAccountByAddress", "0xaccount1", @@ -2105,46 +2003,6 @@ Array [ 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, - }, - ], ] `; @@ -2167,7 +2025,7 @@ Array [ "networkClientId": "arbitrum", "origin": "metamask", "requireApproval": false, - "type": "bridge", + "type": "swap", }, ], ] @@ -2354,60 +2212,36 @@ Array [ Array [ "BridgeController:stopPollingForQuotes", ], - 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, }, ], + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], + Array [ + "NetworkController:findNetworkClientIdByChainId", + "0xa4b1", + ], + Array [ + "GasFeeController:getState", + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], ] `; @@ -2570,7 +2404,7 @@ Array [ "networkClientId": "arbitrum", "origin": "metamask", "requireApproval": false, - "type": "bridge", + "type": "swap", }, ], ] @@ -2581,6 +2415,22 @@ Array [ Array [ "BridgeController:stopPollingForQuotes", ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "gas_included": false, + "price_impact": 0, + "provider": "lifi_across", + "quoted_time_minutes": 0, + "stx_enabled": false, + "token_symbol_destination": "ETH", + "token_symbol_source": "ETH", + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], Array [ "AccountsController:getAccountByAddress", "0xaccount1", @@ -2612,46 +2462,6 @@ Array [ 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, - }, - ], ] `; @@ -2814,7 +2624,7 @@ Array [ "networkClientId": "arbitrum", "origin": "metamask", "requireApproval": false, - "type": "bridge", + "type": "swap", }, ], ] @@ -2825,6 +2635,22 @@ Array [ Array [ "BridgeController:stopPollingForQuotes", ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "gas_included": false, + "price_impact": 0, + "provider": "lifi_across", + "quoted_time_minutes": 0, + "stx_enabled": false, + "token_symbol_destination": "WETH", + "token_symbol_source": "ETH", + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ], Array [ "AccountsController:getAccountByAddress", "0xaccount1", @@ -2842,54 +2668,30 @@ Array [ Array [ "AccountsController:getSelectedMultichainAccount", ], +] +`; + +exports[`BridgeStatusController submitTx: Solana bridge should handle snap controller errors 1`] = ` +Array [ Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", + "BridgeController:stopPollingForQuotes", ], 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", + "provider": "test-bridge_test-bridge", + "quoted_time_minutes": 5, "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, + "token_symbol_destination": "ETH", + "token_symbol_source": "SOL", + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 985, }, ], -] -`; - -exports[`BridgeStatusController submitTx: Solana should successfully submit a Solana transaction 1`] = ` -Array [ - Array [ - "BridgeController:stopPollingForQuotes", - ], Array [ "AccountsController:getSelectedMultichainAccount", ], @@ -2918,56 +2720,78 @@ Array [ ], Array [ "BridgeController:trackUnifiedSwapBridgeEvent", - "Unified SwapBridge Snap Confirmation Page Viewed", - Object {}, - ], - Array [ - "AccountsController:getSelectedMultichainAccount", + "Unified SwapBridge Failed", + Object { + "error_message": "Snap error", + "gas_included": false, + "price_impact": 0, + "provider": "test-bridge_test-bridge", + "quoted_time_minutes": 5, + "stx_enabled": false, + "token_symbol_destination": "ETH", + "token_symbol_source": "SOL", + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 985, + }, ], +] +`; + +exports[`BridgeStatusController submitTx: Solana bridge should successfully submit a transaction 1`] = ` +Array [ Array [ - "AccountsController:getAccountByAddress", - "", + "BridgeController:stopPollingForQuotes", ], Array [ "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", Object { - "action_type": "crosschain-v1", - "actual_time_minutes": 0, - "allowance_reset_transaction": undefined, - "approval_transaction": undefined, - "chain_id_destination": "eip155:1", - "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", - "custom_slippage": true, - "destination_transaction": "PENDING", - "error_message": "error_message", "gas_included": false, - "is_hardware_wallet": false, "price_impact": 0, "provider": "test-bridge_test-bridge", - "quote_vs_execution_ratio": 1, "quoted_time_minutes": 5, - "quoted_vs_used_gas_ratio": 1, - "security_warnings": Array [], - "slippage_limit": 0, - "source_transaction": "PENDING", "stx_enabled": false, - "swap_type": "crosschain", - "token_address_destination": "eip155:1/slip44:60", - "token_address_source": "eip155:1399811149/slip44:501", "token_symbol_destination": "ETH", "token_symbol_source": "SOL", - "usd_actual_gas": 5, - "usd_actual_return": 1000, "usd_amount_source": 100, "usd_quoted_gas": 5, - "usd_quoted_return": 1000, + "usd_quoted_return": 985, + }, + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], + Array [ + "RemoteFeatureFlagController:getState", + ], + Array [ + "SnapController:handleRequest", + Object { + "handler": "onClientRequest", + "origin": "metamask", + "request": Object { + "id": "test-uuid-1234", + "jsonrpc": "2.0", + "method": "signAndSendTransactionWithoutConfirmation", + "params": Object { + "account": Object { + "address": "0x123...", + }, + "scope": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAHDXLY8oVRIwA8ZdRSGjM5RIZJW8Wv+Twyw3NqU4Hov+OHoHp/dmeDvstKbICW3ezeGR69t3/PTAvdXgZVdJFJXaxkoKXUTWfEAyQyCCG9nwVoDsd10OFdnM9ldSi+9SLqHpqWVDV+zzkmftkF//DpbXxqeH8obNXHFR7pUlxG9uNVOn64oNsFdeUvD139j1M51iRmUY839Y25ET4jDRscT081oGb+rLnywLjLSrIQx6MkqNBhCFbxqY1YmoGZVORW/QMGRm/lIRcy/+ytunLDm+e8jOW7xfcSayxDmzpAAAAAjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpBHnVW/IxwG7udMVuzmgVB/2xst6j9I5RArHNola8E4+0P/on9df2SnTAmx8pWHneSwmrNt/J3VFLMhqns4zl6JmXkZ+niuxMhAGrmKBaBo94uMv2Sl+Xh3i+VOO0m5BdNZ1ElenbwQylHQY+VW1ydG1MaUEeNpG+EVgswzPMwPoLBgAFAsBcFQAGAAkDQA0DAAAAAAAHBgABAhMICQAHBgADABYICQEBCAIAAwwCAAAAUEYVOwAAAAAJAQMBEQoUCQADBAETCgsKFw0ODxARAwQACRQj5RfLl3rjrSoBAAAAQ2QAAVBGFTsAAAAAyYZnBwAAAABkAAAJAwMAAAEJDAkAAAIBBBMVCQjGASBMKQwnooTbKNxdBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUHTKomh4KXvNgA0ovYKS5F8GIOBgAAAAAAAAAAAAAAAAAQgAAAAAAAAAAAAAAAAAAAAAAAEIF7RFOAwAAAAAAAAAAAAAAaAIAAAAAAAC4CwAAAAAAAOAA2mcAAAAAAAAAAAAAAAAAAAAApapuIXG0FuHSfsU8qME9s/kaic0AAwGCsZdSuxV5eCm+Ria4LEQPgTg4bg65gNrTAefEzpAfPQgCABIMAgAAAAAAAAAAAAAACAIABQwCAAAAsIOFAAAAAAADWk6DVOZO8lMFQg2r0dgfltD6tRL/B1hH3u00UzZdgqkAAxEqIPdq2eRt/F6mHNmFe7iwZpdrtGmHNJMFlK7c6Bc6k6kjBezr6u/tAgvu3OGsJSwSElmcOHZ21imqH/rhJ2KgqDJdBPFH4SYIM1kBAAA=", + }, + }, + "snapId": "test-snap", }, ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], ] `; -exports[`BridgeStatusController submitTx: Solana should successfully submit a Solana transaction 2`] = ` +exports[`BridgeStatusController submitTx: Solana bridge should successfully submit a transaction 2`] = ` Object { "approvalTxId": undefined, "chainId": "0x416edef1601be", @@ -2976,7 +2800,7 @@ Object { "destinationTokenAmount": "0.5", "destinationTokenDecimals": 18, "destinationTokenSymbol": "ETH", - "hash": undefined, + "hash": "signature", "id": "test-uuid-1234", "isBridgeTx": true, "isSolana": true, @@ -2997,7 +2821,7 @@ Object { } `; -exports[`BridgeStatusController submitTx: Solana should successfully submit a Solana transaction 3`] = ` +exports[`BridgeStatusController submitTx: Solana bridge should successfully submit a transaction 3`] = ` Object { "approvalTxId": undefined, "bridgeTxMeta": Object { @@ -3008,7 +2832,7 @@ Object { "destinationTokenAmount": "0.5", "destinationTokenDecimals": 18, "destinationTokenSymbol": "ETH", - "hash": undefined, + "hash": "signature", "id": "test-uuid-1234", "isBridgeTx": true, "isSolana": true, @@ -3214,65 +3038,175 @@ Object { }, "refuel": false, "srcChainId": 1151111081099710, - "srcTxHash": undefined, + "srcTxHash": "signature", }, } `; -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`] = ` +exports[`BridgeStatusController submitTx: Solana bridge should throw error when snap ID is missing 1`] = ` Array [ Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", + "BridgeController:stopPollingForQuotes", ], Array [ "BridgeController:trackUnifiedSwapBridgeEvent", - "Unified SwapBridge Completed", + "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.25, - "quoted_vs_used_gas_ratio": 1, - "security_warnings": Array [], - "slippage_limit": 0, - "source_transaction": "COMPLETE", + "provider": "test-bridge_test-bridge", + "quoted_time_minutes": 5, "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, + "token_symbol_source": "SOL", + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 985, + }, + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Failed", + Object { + "error_message": "Failed to submit cross-chain swap transaction: undefined snap id", + "gas_included": false, + "price_impact": 0, + "provider": "test-bridge_test-bridge", + "quoted_time_minutes": 5, + "stx_enabled": false, + "token_symbol_destination": "ETH", + "token_symbol_source": "SOL", + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 985, }, ], ] `; -exports[`BridgeStatusController subscription handlers TransactionController:transactionConfirmed should not track completed event for other transaction types 1`] = `Array []`; +exports[`BridgeStatusController submitTx: Solana swap should handle snap controller errors 1`] = ` +Array [ + Array [ + "BridgeController:stopPollingForQuotes", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "gas_included": false, + "price_impact": 0, + "provider": "test-bridge_undefined", + "quoted_time_minutes": 5, + "stx_enabled": false, + "token_symbol_destination": "USDC", + "token_symbol_source": "SOL", + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 985, + }, + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], + Array [ + "RemoteFeatureFlagController:getState", + ], + Array [ + "SnapController:handleRequest", + Object { + "handler": "onClientRequest", + "origin": "metamask", + "request": Object { + "id": "test-uuid-1234", + "jsonrpc": "2.0", + "method": "signAndSendTransactionWithoutConfirmation", + "params": Object { + "account": Object { + "address": "0x123...", + }, + "scope": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAHDXLY8oVRIwA8ZdRSGjM5RIZJW8Wv+Twyw3NqU4Hov+OHoHp/dmeDvstKbICW3ezeGR69t3/PTAvdXgZVdJFJXaxkoKXUTWfEAyQyCCG9nwVoDsd10OFdnM9ldSi+9SLqHpqWVDV+zzkmftkF//DpbXxqeH8obNXHFR7pUlxG9uNVOn64oNsFdeUvD139j1M51iRmUY839Y25ET4jDRscT081oGb+rLnywLjLSrIQx6MkqNBhCFbxqY1YmoGZVORW/QMGRm/lIRcy/+ytunLDm+e8jOW7xfcSayxDmzpAAAAAjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpBHnVW/IxwG7udMVuzmgVB/2xst6j9I5RArHNola8E4+0P/on9df2SnTAmx8pWHneSwmrNt/J3VFLMhqns4zl6JmXkZ+niuxMhAGrmKBaBo94uMv2Sl+Xh3i+VOO0m5BdNZ1ElenbwQylHQY+VW1ydG1MaUEeNpG+EVgswzPMwPoLBgAFAsBcFQAGAAkDQA0DAAAAAAAHBgABAhMICQAHBgADABYICQEBCAIAAwwCAAAAUEYVOwAAAAAJAQMBEQoUCQADBAETCgsKFw0ODxARAwQACRQj5RfLl3rjrSoBAAAAQ2QAAVBGFTsAAAAAyYZnBwAAAABkAAAJAwMAAAEJDAkAAAIBBBMVCQjGASBMKQwnooTbKNxdBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUHTKomh4KXvNgA0ovYKS5F8GIOBgAAAAAAAAAAAAAAAAAQgAAAAAAAAAAAAAAAAAAAAAAAEIF7RFOAwAAAAAAAAAAAAAAaAIAAAAAAAC4CwAAAAAAAOAA2mcAAAAAAAAAAAAAAAAAAAAApapuIXG0FuHSfsU8qME9s/kaic0AAwGCsZdSuxV5eCm+Ria4LEQPgTg4bg65gNrTAefEzpAfPQgCABIMAgAAAAAAAAAAAAAACAIABQwCAAAAsIOFAAAAAAADWk6DVOZO8lMFQg2r0dgfltD6tRL/B1hH3u00UzZdgqkAAxEqIPdq2eRt/F6mHNmFe7iwZpdrtGmHNJMFlK7c6Bc6k6kjBezr6u/tAgvu3OGsJSwSElmcOHZ21imqH/rhJ2KgqDJdBPFH4SYIM1kBAAA=", + }, + }, + "snapId": "test-snap", + }, + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Failed", + Object { + "error_message": "Snap error", + "gas_included": false, + "price_impact": 0, + "provider": "test-bridge_undefined", + "quoted_time_minutes": 5, + "stx_enabled": false, + "token_symbol_destination": "USDC", + "token_symbol_source": "SOL", + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 985, + }, + ], +] +`; -exports[`BridgeStatusController subscription handlers TransactionController:transactionConfirmed should track completed event for swap transaction 1`] = ` +exports[`BridgeStatusController submitTx: Solana swap should successfully submit a transaction 1`] = ` Array [ + Array [ + "BridgeController:stopPollingForQuotes", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "gas_included": false, + "price_impact": 0, + "provider": "test-bridge_undefined", + "quoted_time_minutes": 5, + "stx_enabled": false, + "token_symbol_destination": "USDC", + "token_symbol_source": "SOL", + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 985, + }, + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], + Array [ + "RemoteFeatureFlagController:getState", + ], + Array [ + "SnapController:handleRequest", + Object { + "handler": "onClientRequest", + "origin": "metamask", + "request": Object { + "id": "test-uuid-1234", + "jsonrpc": "2.0", + "method": "signAndSendTransactionWithoutConfirmation", + "params": Object { + "account": Object { + "address": "0x123...", + }, + "scope": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAHDXLY8oVRIwA8ZdRSGjM5RIZJW8Wv+Twyw3NqU4Hov+OHoHp/dmeDvstKbICW3ezeGR69t3/PTAvdXgZVdJFJXaxkoKXUTWfEAyQyCCG9nwVoDsd10OFdnM9ldSi+9SLqHpqWVDV+zzkmftkF//DpbXxqeH8obNXHFR7pUlxG9uNVOn64oNsFdeUvD139j1M51iRmUY839Y25ET4jDRscT081oGb+rLnywLjLSrIQx6MkqNBhCFbxqY1YmoGZVORW/QMGRm/lIRcy/+ytunLDm+e8jOW7xfcSayxDmzpAAAAAjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpBHnVW/IxwG7udMVuzmgVB/2xst6j9I5RArHNola8E4+0P/on9df2SnTAmx8pWHneSwmrNt/J3VFLMhqns4zl6JmXkZ+niuxMhAGrmKBaBo94uMv2Sl+Xh3i+VOO0m5BdNZ1ElenbwQylHQY+VW1ydG1MaUEeNpG+EVgswzPMwPoLBgAFAsBcFQAGAAkDQA0DAAAAAAAHBgABAhMICQAHBgADABYICQEBCAIAAwwCAAAAUEYVOwAAAAAJAQMBEQoUCQADBAETCgsKFw0ODxARAwQACRQj5RfLl3rjrSoBAAAAQ2QAAVBGFTsAAAAAyYZnBwAAAABkAAAJAwMAAAEJDAkAAAIBBBMVCQjGASBMKQwnooTbKNxdBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUHTKomh4KXvNgA0ovYKS5F8GIOBgAAAAAAAAAAAAAAAAAQgAAAAAAAAAAAAAAAAAAAAAAAEIF7RFOAwAAAAAAAAAAAAAAaAIAAAAAAAC4CwAAAAAAAOAA2mcAAAAAAAAAAAAAAAAAAAAApapuIXG0FuHSfsU8qME9s/kaic0AAwGCsZdSuxV5eCm+Ria4LEQPgTg4bg65gNrTAefEzpAfPQgCABIMAgAAAAAAAAAAAAAACAIABQwCAAAAsIOFAAAAAAADWk6DVOZO8lMFQg2r0dgfltD6tRL/B1hH3u00UzZdgqkAAxEqIPdq2eRt/F6mHNmFe7iwZpdrtGmHNJMFlK7c6Bc6k6kjBezr6u/tAgvu3OGsJSwSElmcOHZ21imqH/rhJ2KgqDJdBPFH4SYIM1kBAAA=", + }, + }, + "snapId": "test-snap", + }, + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], Array [ "AccountsController:getAccountByAddress", - "0xaccount1", + "0x123...", ], Array [ "BridgeController:trackUnifiedSwapBridgeEvent", @@ -3282,126 +3216,235 @@ Array [ "actual_time_minutes": 0, "allowance_reset_transaction": undefined, "approval_transaction": undefined, - "chain_id_destination": "eip155:42161", - "chain_id_source": "eip155:42161", + "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "custom_slippage": true, "destination_transaction": "PENDING", - "error_message": "error_message", "gas_included": false, "is_hardware_wallet": false, "price_impact": 0, - "provider": "lifi_across", + "provider": "test-bridge_undefined", "quote_vs_execution_ratio": 1, - "quoted_time_minutes": 0.25, + "quoted_time_minutes": 5, "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, + "token_address_destination": "eip155:1399811149/slip44:501", + "token_address_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501", + "token_symbol_destination": "USDC", + "token_symbol_source": "SOL", + "usd_actual_gas": 5, + "usd_actual_return": 1000, + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 1000, }, ], ] `; -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 submitTx: Solana swap should successfully submit a transaction 2`] = ` +Object { + "approvalTxId": undefined, + "chainId": "0x416edef1601be", + "destinationChainId": "0x416edef1601be", + "destinationTokenAddress": "0x...", + "destinationTokenAmount": "0.5", + "destinationTokenDecimals": 18, + "destinationTokenSymbol": "USDC", + "hash": "signature", + "id": "test-uuid-1234", + "isBridgeTx": false, + "isSolana": true, + "networkClientId": "test-snap", + "origin": "test-snap", + "sourceTokenAddress": "native", + "sourceTokenAmount": "1000000000", + "sourceTokenDecimals": 9, + "sourceTokenSymbol": "SOL", + "status": "submitted", + "swapTokenValue": "1", + "time": 1234567890, + "txParams": Object { + "data": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAHDXLY8oVRIwA8ZdRSGjM5RIZJW8Wv+Twyw3NqU4Hov+OHoHp/dmeDvstKbICW3ezeGR69t3/PTAvdXgZVdJFJXaxkoKXUTWfEAyQyCCG9nwVoDsd10OFdnM9ldSi+9SLqHpqWVDV+zzkmftkF//DpbXxqeH8obNXHFR7pUlxG9uNVOn64oNsFdeUvD139j1M51iRmUY839Y25ET4jDRscT081oGb+rLnywLjLSrIQx6MkqNBhCFbxqY1YmoGZVORW/QMGRm/lIRcy/+ytunLDm+e8jOW7xfcSayxDmzpAAAAAjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpBHnVW/IxwG7udMVuzmgVB/2xst6j9I5RArHNola8E4+0P/on9df2SnTAmx8pWHneSwmrNt/J3VFLMhqns4zl6JmXkZ+niuxMhAGrmKBaBo94uMv2Sl+Xh3i+VOO0m5BdNZ1ElenbwQylHQY+VW1ydG1MaUEeNpG+EVgswzPMwPoLBgAFAsBcFQAGAAkDQA0DAAAAAAAHBgABAhMICQAHBgADABYICQEBCAIAAwwCAAAAUEYVOwAAAAAJAQMBEQoUCQADBAETCgsKFw0ODxARAwQACRQj5RfLl3rjrSoBAAAAQ2QAAVBGFTsAAAAAyYZnBwAAAABkAAAJAwMAAAEJDAkAAAIBBBMVCQjGASBMKQwnooTbKNxdBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUHTKomh4KXvNgA0ovYKS5F8GIOBgAAAAAAAAAAAAAAAAAQgAAAAAAAAAAAAAAAAAAAAAAAEIF7RFOAwAAAAAAAAAAAAAAaAIAAAAAAAC4CwAAAAAAAOAA2mcAAAAAAAAAAAAAAAAAAAAApapuIXG0FuHSfsU8qME9s/kaic0AAwGCsZdSuxV5eCm+Ria4LEQPgTg4bg65gNrTAefEzpAfPQgCABIMAgAAAAAAAAAAAAAACAIABQwCAAAAsIOFAAAAAAADWk6DVOZO8lMFQg2r0dgfltD6tRL/B1hH3u00UzZdgqkAAxEqIPdq2eRt/F6mHNmFe7iwZpdrtGmHNJMFlK7c6Bc6k6kjBezr6u/tAgvu3OGsJSwSElmcOHZ21imqH/rhJ2KgqDJdBPFH4SYIM1kBAAA=", + "from": "0x123...", + }, + "type": "swap", +} +`; -exports[`BridgeStatusController subscription handlers TransactionController:transactionFailed should not track failed event for signed status 1`] = `Array []`; +exports[`BridgeStatusController submitTx: Solana swap should throw error when account is missing 1`] = ` +Array [ + Array [ + "BridgeController:stopPollingForQuotes", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "gas_included": false, + "price_impact": 0, + "provider": "test-bridge_undefined", + "quoted_time_minutes": 5, + "stx_enabled": false, + "token_symbol_destination": "USDC", + "token_symbol_source": "SOL", + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 985, + }, + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Failed", + Object { + "error_message": "Failed to submit cross-chain swap transaction: undefined multichain account", + "gas_included": false, + "price_impact": 0, + "provider": "test-bridge_undefined", + "quoted_time_minutes": 5, + "stx_enabled": false, + "token_symbol_destination": "USDC", + "token_symbol_source": "SOL", + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 985, + }, + ], +] +`; -exports[`BridgeStatusController subscription handlers TransactionController:transactionFailed should track failed event for bridge transaction 1`] = ` +exports[`BridgeStatusController submitTx: Solana swap should throw error when snap ID is missing 1`] = ` Array [ Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", + "BridgeController:stopPollingForQuotes", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { + "gas_included": false, + "price_impact": 0, + "provider": "test-bridge_undefined", + "quoted_time_minutes": 5, + "stx_enabled": false, + "token_symbol_destination": "USDC", + "token_symbol_source": "SOL", + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 985, + }, + ], + Array [ + "AccountsController:getSelectedMultichainAccount", ], 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", + "error_message": "Failed to submit cross-chain swap transaction: undefined snap id", + "gas_included": false, + "price_impact": 0, + "provider": "test-bridge_undefined", + "quoted_time_minutes": 5, + "stx_enabled": false, + "token_symbol_destination": "USDC", + "token_symbol_source": "SOL", + "usd_amount_source": 100, + "usd_quoted_gas": 5, + "usd_quoted_return": 985, + }, + ], +] +`; + +exports[`BridgeStatusController subscription handlers should not track completed event for other transaction types 1`] = `Array []`; + +exports[`BridgeStatusController subscription handlers should not track failed event for approved status 1`] = `Array []`; + +exports[`BridgeStatusController subscription handlers should not track failed event for other transaction types 1`] = `Array []`; + +exports[`BridgeStatusController subscription handlers should not track failed event for signed status 1`] = `Array []`; + +exports[`BridgeStatusController subscription handlers should throw error when txMeta is undefined 1`] = ` +Array [ + Array [ + "BridgeController:stopPollingForQuotes", + ], + Array [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Submitted", + Object { "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", + "quoted_time_minutes": 0, "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, }, ], + Array [ + "NetworkController:getState", + ], + Array [ + "NetworkController:getNetworkClientById", + "mainnet", + ], + Array [ + "AccountsController:getSelectedMultichainAccount", + ], ] `; -exports[`BridgeStatusController subscription handlers TransactionController:transactionFailed should track failed event for swap transaction 1`] = ` +exports[`BridgeStatusController subscription handlers should track completed event for swap transaction 1`] = ` Array [ Array [ "AccountsController:getAccountByAddress", "0xaccount1", ], +] +`; + +exports[`BridgeStatusController subscription handlers should track completed event for swap transaction if it is not in txHistory 1`] = ` +Array [ Array [ "BridgeController:trackUnifiedSwapBridgeEvent", - "Unified SwapBridge Failed", + "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", + "chain_id_destination": "eip155:10", + "chain_id_source": "eip155:10", + "custom_slippage": false, + "error_message": undefined, "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, + "provider": "", + "quote_vs_execution_ratio": 0, + "quoted_time_minutes": 0, + "quoted_vs_used_gas_ratio": 0, "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", + "token_address_destination": "eip155:10/slip44:60", + "token_address_source": "eip155:10/slip44:60", + "token_symbol_destination": "", + "token_symbol_source": "", "usd_actual_gas": 0, "usd_actual_return": 0, - "usd_amount_source": 0, + "usd_amount_source": 100, "usd_quoted_gas": 0, "usd_quoted_return": 0, }, @@ -3409,6 +3452,24 @@ Array [ ] `; +exports[`BridgeStatusController subscription handlers should track failed event for bridge transaction 1`] = ` +Array [ + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], +] +`; + +exports[`BridgeStatusController subscription handlers should track failed event for swap transaction 1`] = ` +Array [ + Array [ + "AccountsController:getAccountByAddress", + "0xaccount1", + ], +] +`; + 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 904dc7a4cb2..ed923a6002a 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -50,7 +50,6 @@ 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', @@ -579,23 +578,6 @@ const executePollingWithPendingStatus = async () => { // Define mocks at the top level const mockFetchFn = jest.fn(); -const mockMessengerCall = jest.fn().mockImplementation((method: string) => { - if (method === 'RemoteFeatureFlagController:getState') { - return { - remoteFeatureFlags: { - bridgeConfig: { - support: true, - chains: { - [ChainId.SOLANA]: { - isSnapConfirmationEnabled: false, - }, - }, - }, - }, - }; - } - return null; -}); const mockSelectedAccount = { id: 'test-account-id', address: '0xaccount1', @@ -1334,7 +1316,7 @@ describe('BridgeStatusController', () => { }); }); - describe('submitTx: Solana', () => { + describe('submitTx: Solana bridge', () => { const mockQuoteResponse: QuoteResponse & QuoteMetadata = { quote: { requestId: '123', @@ -1449,25 +1431,41 @@ describe('BridgeStatusController', () => { snap: { id: 'test-snap', }, + keyring: { + type: 'any', + }, }, options: { scope: 'solana-chain-id' }, }; + let mockMessengerCall: jest.Mock; beforeEach(() => { jest.clearAllMocks(); + jest.clearAllTimers(); jest.spyOn(Date, 'now').mockReturnValue(1234567890); + mockMessengerCall = jest.fn(); + mockMessengerCall.mockImplementationOnce(jest.fn()); // stopPollingForQuotes + mockMessengerCall.mockImplementationOnce(jest.fn()); // track event }); - it('should successfully submit a Solana transaction', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes + it('should successfully submit a transaction', async () => { mockMessengerCall.mockReturnValueOnce(mockSolanaAccount); + // Mock the RemoteFeatureFlagController:getState call that happens in getBridgeFeatureFlags + mockMessengerCall.mockReturnValueOnce({ + remoteFeatureFlags: { + cacheTimestamp: 1234567890, + bridgeConfig: { + support: true, + chains: { + [ChainId.SOLANA]: { + isSnapConfirmationEnabled: true, + }, + }, + }, + }, + }); mockMessengerCall.mockResolvedValueOnce('signature'); - mockMessengerCall.mockReturnValueOnce(mockSolanaAccount); - mockMessengerCall.mockReturnValueOnce(mockSolanaAccount); - - mockMessengerCall.mockResolvedValueOnce('tokens'); - mockMessengerCall.mockResolvedValueOnce('tokens'); const { controller, startPollingForBridgeTxStatusSpy } = getController(mockMessengerCall); @@ -1483,12 +1481,25 @@ describe('BridgeStatusController', () => { }); it('should throw error when snap ID is missing', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // stopPollingForQuotes const accountWithoutSnap = { - ...mockSelectedAccount, + ...mockSolanaAccount, metadata: { snap: undefined }, }; mockMessengerCall.mockReturnValueOnce(accountWithoutSnap); + // Mock the RemoteFeatureFlagController:getState call that happens in getBridgeFeatureFlags + mockMessengerCall.mockReturnValueOnce({ + remoteFeatureFlags: { + bridgeConfig: { + support: true, + chains: { + [ChainId.SOLANA]: { + isSnapConfirmationEnabled: false, + }, + }, + }, + }, + }); + mockMessengerCall.mockReturnValueOnce(accountWithoutSnap); const { controller, startPollingForBridgeTxStatusSpy } = getController(mockMessengerCall); @@ -1499,6 +1510,7 @@ describe('BridgeStatusController', () => { 'Failed to submit cross-chain swap transaction: undefined snap id', ); expect(startPollingForBridgeTxStatusSpy).not.toHaveBeenCalled(); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); }); it('should throw error when account is missing', async () => { @@ -1516,7 +1528,6 @@ describe('BridgeStatusController', () => { }); it('should handle snap controller errors', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes mockMessengerCall.mockReturnValueOnce(mockSolanaAccount); // Mock the RemoteFeatureFlagController:getState call that happens in getBridgeFeatureFlags mockMessengerCall.mockReturnValueOnce({ @@ -1539,25 +1550,231 @@ describe('BridgeStatusController', () => { await expect( controller.submitTx(mockQuoteResponse, false), ).rejects.toThrow('Snap error'); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).not.toHaveBeenCalled(); }); + }); - it('should throw error when txMeta is undefined', async () => { - mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); - mockMessengerCall.mockResolvedValueOnce('0xabc...'); + describe('submitTx: Solana swap', () => { + const mockQuoteResponse: QuoteResponse & QuoteMetadata = { + quote: { + requestId: '123', + srcChainId: ChainId.SOLANA, + destChainId: ChainId.SOLANA, + srcTokenAmount: '1000000000', + srcAsset: { + chainId: ChainId.SOLANA, + address: 'native', + symbol: 'SOL', + name: 'Solana', + decimals: 9, + assetId: getNativeAssetForChainId(ChainId.SOLANA).assetId, + }, + destTokenAmount: '0.5', + destAsset: { + chainId: ChainId.SOLANA, + address: '0x...', + symbol: 'USDC', + name: 'USDC', + decimals: 18, + assetId: 'eip155:1399811149/slip44:501', + }, + bridgeId: 'test-bridge', + bridges: [], + steps: [ + { + action: ActionTypes.BRIDGE, + srcChainId: ChainId.SOLANA, + destChainId: ChainId.ETH, + srcAsset: { + chainId: ChainId.SOLANA, + address: 'native', + symbol: 'SOL', + name: 'Solana', + decimals: 9, + assetId: 'eip155:1399811149/slip44:501', + }, + destAsset: { + chainId: ChainId.ETH, + address: '0x...', + symbol: 'ETH', + name: 'Ethereum', + decimals: 18, + assetId: 'eip155:1/slip44:60', + }, + srcAmount: '1000000000', + destAmount: '0.5', + protocol: { + name: 'test-protocol', + displayName: 'Test Protocol', + icon: 'test-icon', + }, + }, + ], + feeData: { + [FeeType.METABRIDGE]: { + amount: '1000000', + asset: { + chainId: ChainId.SOLANA, + address: 'native', + symbol: 'SOL', + name: 'Solana', + decimals: 9, + assetId: 'eip155:1399811149/slip44:501', + }, + }, + }, + }, + estimatedProcessingTimeInSeconds: 300, + trade: + 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAHDXLY8oVRIwA8ZdRSGjM5RIZJW8Wv+Twyw3NqU4Hov+OHoHp/dmeDvstKbICW3ezeGR69t3/PTAvdXgZVdJFJXaxkoKXUTWfEAyQyCCG9nwVoDsd10OFdnM9ldSi+9SLqHpqWVDV+zzkmftkF//DpbXxqeH8obNXHFR7pUlxG9uNVOn64oNsFdeUvD139j1M51iRmUY839Y25ET4jDRscT081oGb+rLnywLjLSrIQx6MkqNBhCFbxqY1YmoGZVORW/QMGRm/lIRcy/+ytunLDm+e8jOW7xfcSayxDmzpAAAAAjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpBHnVW/IxwG7udMVuzmgVB/2xst6j9I5RArHNola8E4+0P/on9df2SnTAmx8pWHneSwmrNt/J3VFLMhqns4zl6JmXkZ+niuxMhAGrmKBaBo94uMv2Sl+Xh3i+VOO0m5BdNZ1ElenbwQylHQY+VW1ydG1MaUEeNpG+EVgswzPMwPoLBgAFAsBcFQAGAAkDQA0DAAAAAAAHBgABAhMICQAHBgADABYICQEBCAIAAwwCAAAAUEYVOwAAAAAJAQMBEQoUCQADBAETCgsKFw0ODxARAwQACRQj5RfLl3rjrSoBAAAAQ2QAAVBGFTsAAAAAyYZnBwAAAABkAAAJAwMAAAEJDAkAAAIBBBMVCQjGASBMKQwnooTbKNxdBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUHTKomh4KXvNgA0ovYKS5F8GIOBgAAAAAAAAAAAAAAAAAQgAAAAAAAAAAAAAAAAAAAAAAAEIF7RFOAwAAAAAAAAAAAAAAaAIAAAAAAAC4CwAAAAAAAOAA2mcAAAAAAAAAAAAAAAAAAAAApapuIXG0FuHSfsU8qME9s/kaic0AAwGCsZdSuxV5eCm+Ria4LEQPgTg4bg65gNrTAefEzpAfPQgCABIMAgAAAAAAAAAAAAAACAIABQwCAAAAsIOFAAAAAAADWk6DVOZO8lMFQg2r0dgfltD6tRL/B1hH3u00UzZdgqkAAxEqIPdq2eRt/F6mHNmFe7iwZpdrtGmHNJMFlK7c6Bc6k6kjBezr6u/tAgvu3OGsJSwSElmcOHZ21imqH/rhJ2KgqDJdBPFH4SYIM1kBAAA=', + sentAmount: { + amount: '1', + valueInCurrency: '100', + usd: '100', + }, + toTokenAmount: { + amount: '0.5', + valueInCurrency: '1000', + usd: '1000', + }, + totalNetworkFee: { + amount: '0.1', + valueInCurrency: '10', + usd: '10', + }, + totalMaxNetworkFee: { + amount: '0.15', + valueInCurrency: '15', + usd: '15', + }, + gasFee: { + amount: '0.05', + valueInCurrency: '5', + usd: '5', + }, + adjustedReturn: { + valueInCurrency: '985', + usd: '985', + }, + cost: { + valueInCurrency: '15', + usd: '15', + }, + swapRate: '0.5', + }; + + const mockSolanaAccount = { + address: '0x123...', + metadata: { + snap: { + id: 'test-snap', + }, + keyring: { + type: 'Hardware', + }, + }, + options: { scope: 'solana-chain-id' }, + }; + const mockMessengerCall = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + jest.clearAllTimers(); + jest.spyOn(Date, 'now').mockReturnValue(1234567890); + mockMessengerCall.mockImplementationOnce(jest.fn()); // stopPollingForQuotes + mockMessengerCall.mockImplementationOnce(jest.fn()); // track event + }); + + it('should successfully submit a transaction', async () => { + mockMessengerCall.mockReturnValueOnce(mockSolanaAccount); + // Mock the RemoteFeatureFlagController:getState call that happens in getBridgeFeatureFlags + mockMessengerCall.mockReturnValueOnce({ + remoteFeatureFlags: { + bridgeConfig: { + support: true, + chains: { + [ChainId.SOLANA]: { + isSnapConfirmationEnabled: false, + }, + }, + }, + }, + }); + mockMessengerCall.mockResolvedValueOnce({ + signature: 'signature', + }); + + mockMessengerCall.mockReturnValueOnce(mockSolanaAccount); + + const { controller, startPollingForBridgeTxStatusSpy } = + getController(mockMessengerCall); + const result = await controller.submitTx(mockQuoteResponse, false); + controller.stopAllPolling(); + + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); + expect(result).toMatchSnapshot(); + expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + }); + + it('should throw error when snap ID is missing', async () => { + const accountWithoutSnap = { + ...mockSolanaAccount, + metadata: { snap: undefined }, + }; + mockMessengerCall.mockReturnValueOnce(accountWithoutSnap); const { controller, startPollingForBridgeTxStatusSpy } = getController(mockMessengerCall); await expect( - controller.submitTx( - { - ...mockQuoteResponse, - trade: {} as never, + controller.submitTx(mockQuoteResponse, false), + ).rejects.toThrow( + 'Failed to submit cross-chain swap transaction: undefined snap id', + ); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); + expect(startPollingForBridgeTxStatusSpy).not.toHaveBeenCalled(); + }); + + it('should throw error when account is missing', async () => { + mockMessengerCall.mockReturnValueOnce(undefined); + + const { controller, startPollingForBridgeTxStatusSpy } = + getController(mockMessengerCall); + + await expect( + controller.submitTx(mockQuoteResponse, false), + ).rejects.toThrow( + 'Failed to submit cross-chain swap transaction: undefined multichain account', + ); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); + expect(startPollingForBridgeTxStatusSpy).not.toHaveBeenCalled(); + }); + + it('should handle snap controller errors', async () => { + mockMessengerCall.mockReturnValueOnce(mockSolanaAccount); + // Mock the RemoteFeatureFlagController:getState call that happens in getBridgeFeatureFlags + mockMessengerCall.mockReturnValueOnce({ + remoteFeatureFlags: { + bridgeConfig: { + support: true, + chains: { + [ChainId.SOLANA]: { + isSnapConfirmationEnabled: false, + }, + }, }, - false, - ), - ).rejects.toThrow('Failed to submit bridge tx: txMeta is undefined'); + }, + }); + mockMessengerCall.mockRejectedValueOnce(new Error('Snap error')); + + const { controller, startPollingForBridgeTxStatusSpy } = + getController(mockMessengerCall); + + await expect( + controller.submitTx(mockQuoteResponse, false), + ).rejects.toThrow('Snap error'); + expect(mockMessengerCall.mock.calls).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).not.toHaveBeenCalled(); }); }); @@ -1574,7 +1791,11 @@ describe('BridgeStatusController', () => { 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 }, + totalMaxNetworkFee: { + amount: '1.234', + valueInCurrency: null, + usd: null, + }, gasFee: { amount: '1.234', valueInCurrency: null, usd: null }, adjustedReturn: { valueInCurrency: null, usd: null }, swapRate: '1.234', @@ -1640,10 +1861,15 @@ describe('BridgeStatusController', () => { }, }; + const mockMessengerCall = jest.fn(); + beforeEach(() => { jest.clearAllMocks(); + jest.clearAllTimers(); jest.spyOn(Date, 'now').mockReturnValue(1234567890); jest.spyOn(Math, 'random').mockReturnValue(0.456); + mockMessengerCall.mockImplementationOnce(jest.fn()); // stopPollingForQuotes + mockMessengerCall.mockImplementationOnce(jest.fn()); // track event }); const setupApprovalMocks = () => { @@ -1681,7 +1907,6 @@ describe('BridgeStatusController', () => { }; it('should successfully submit an EVM bridge transaction with approval', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes setupApprovalMocks(); setupBridgeMocks(); @@ -1707,7 +1932,6 @@ describe('BridgeStatusController', () => { }); it('should successfully submit an EVM bridge transaction with no approval', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes setupBridgeMocks(); const { controller, startPollingForBridgeTxStatusSpy } = @@ -1752,7 +1976,6 @@ describe('BridgeStatusController', () => { }); it('should handle smart transactions', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes setupBridgeMocks(); const { controller, startPollingForBridgeTxStatusSpy } = @@ -1779,7 +2002,6 @@ describe('BridgeStatusController', () => { }); it('should handle smart accounts (4337)', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes mockMessengerCall.mockReturnValueOnce({ ...mockSelectedAccount, type: EthAccountType.Erc4337, @@ -1845,7 +2067,6 @@ describe('BridgeStatusController', () => { }); it('should reset USDT allowance', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes mockIsEthUsdt.mockReturnValueOnce(true); // USDT approval reset @@ -1880,7 +2101,6 @@ describe('BridgeStatusController', () => { }); it('should throw an error if approval tx fails', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); mockMessengerCall.mockReturnValueOnce('arbitrum-client-id'); mockMessengerCall.mockReturnValueOnce({ @@ -1903,7 +2123,6 @@ describe('BridgeStatusController', () => { }); it('should throw an error if approval tx meta is undefined', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); mockMessengerCall.mockReturnValueOnce('arbitrum-client-id'); mockMessengerCall.mockReturnValueOnce({ @@ -1912,12 +2131,13 @@ describe('BridgeStatusController', () => { estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); addTransactionFn.mockResolvedValueOnce({ transactionMeta: undefined, - result: undefined, + result: new Promise((resolve) => resolve('0xevmTxHash')), }); mockMessengerCall.mockReturnValueOnce({ transactions: [], }); + setupBridgeMocks(); const { controller, startPollingForBridgeTxStatusSpy } = getController(mockMessengerCall); @@ -1934,7 +2154,6 @@ describe('BridgeStatusController', () => { }); it('should delay after submitting linea approval', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes const handleLineaDelaySpy = jest .spyOn(transactionUtils, 'handleLineaDelay') .mockResolvedValueOnce(); @@ -1953,7 +2172,10 @@ describe('BridgeStatusController', () => { const lineaQuoteResponse = { ...mockEvmQuoteResponse, quote: { ...mockEvmQuoteResponse.quote, srcChainId: 59144 }, - trade: { ...mockEvmQuoteResponse.trade, gasLimit: undefined } as never, + trade: { + ...mockEvmQuoteResponse.trade, + gasLimit: undefined, + } as never, }; const result = await controller.submitTx(lineaQuoteResponse, false); @@ -1989,7 +2211,11 @@ describe('BridgeStatusController', () => { 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 }, + totalMaxNetworkFee: { + amount: '1.234', + valueInCurrency: null, + usd: null, + }, gasFee: { amount: '1.234', valueInCurrency: null, usd: null }, adjustedReturn: { valueInCurrency: null, usd: null }, swapRate: '1.234', @@ -2054,11 +2280,14 @@ describe('BridgeStatusController', () => { }, }, }; + const mockMessengerCall = jest.fn(); beforeEach(() => { jest.clearAllMocks(); jest.spyOn(Date, 'now').mockReturnValue(1234567890); jest.spyOn(Math, 'random').mockReturnValue(0.456); + mockMessengerCall.mockImplementationOnce(jest.fn()); // stopPollingForQuotes + mockMessengerCall.mockImplementationOnce(jest.fn()); // track event }); const setupApprovalMocks = () => { @@ -2096,7 +2325,6 @@ describe('BridgeStatusController', () => { }; it('should successfully submit an EVM swap transaction with approval', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes setupApprovalMocks(); setupBridgeMocks(); @@ -2122,7 +2350,6 @@ describe('BridgeStatusController', () => { }); it('should successfully submit an EVM swap transaction with no approval', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes setupBridgeMocks(); const { controller, startPollingForBridgeTxStatusSpy } = @@ -2167,8 +2394,17 @@ describe('BridgeStatusController', () => { }); it('should handle smart transactions', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes - 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(mockSelectedAccount); const { controller, startPollingForBridgeTxStatusSpy } = getController(mockMessengerCall); @@ -2194,7 +2430,6 @@ describe('BridgeStatusController', () => { }); it('should handle smart accounts (4337)', async () => { - mockMessengerCall.mockImplementationOnce(jest.fn()); // BridgeController:stopPollingForQuotes mockMessengerCall.mockReturnValueOnce({ ...mockSelectedAccount, type: EthAccountType.Erc4337, @@ -2250,7 +2485,6 @@ describe('BridgeStatusController', () => { | BridgeStatusControllerEvents | TransactionControllerEvents | BridgeControllerEvents - | MultichainTransactionsControllerEvents >; beforeEach(() => { @@ -2262,7 +2496,6 @@ describe('BridgeStatusController', () => { | BridgeStatusControllerEvents | TransactionControllerEvents | BridgeControllerEvents - | MultichainTransactionsControllerEvents >(); jest.spyOn(mockMessenger, 'call').mockImplementation((...args) => { @@ -2280,7 +2513,6 @@ describe('BridgeStatusController', () => { allowedEvents: [ 'TransactionController:transactionFailed', 'TransactionController:transactionConfirmed', - 'MultichainTransactionsController:transactionConfirmed', ], }) as never; diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 79be421caa9..f99723329aa 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -58,9 +58,12 @@ import { import { getTxGasEstimates } from './utils/gas'; import { getFinalizedTxProperties, + getPriceImpactFromQuote, getRequestMetadataFromHistory, getRequestParamFromHistory, getTradeDataFromHistory, + getTradeDataFromQuote, + getEVMTxPropertiesFromTransactionMeta, getTxStatusesFromHistory, } from './utils/metrics'; import { @@ -192,6 +195,7 @@ export class BridgeStatusController extends StaticIntervalPollingController { - const { type, id } = transactionMeta; - if (type === TransactionType.swap) { - this.#trackUnifiedSwapBridgeEvent( - UnifiedSwapBridgeEventName.Completed, - id, + getEVMTxPropertiesFromTransactionMeta(transactionMeta), ); } }, @@ -571,7 +563,7 @@ export class BridgeStatusController extends StaticIntervalPollingController }; + )) as string | { result: Record } | { signature: string }; // The extension client actually redirects before it can do anytyhing with this meta const txMeta = handleSolanaTxResponse( @@ -851,6 +843,23 @@ export class BridgeStatusController extends StaticIntervalPollingController> => { this.messagingSystem.call('BridgeController:stopPollingForQuotes'); + // Before the tx iks confirmed, its data is not available in txHistory + // The quote is used to populate event properties before confirmation + const preConfirmationProperties = { + ...getPriceImpactFromQuote(quoteResponse.quote), + ...getTradeDataFromQuote(quoteResponse), + token_symbol_source: quoteResponse.quote.srcAsset.symbol, + token_symbol_destination: quoteResponse.quote.destAsset.symbol, + usd_amount_source: Number(quoteResponse.sentAmount?.usd ?? 0), + stx_enabled: isStxEnabledOnClient, + }; + // Emit Submitted event after submit button is clicked + this.#trackUnifiedSwapBridgeEvent( + UnifiedSwapBridgeEventName.Submitted, + undefined, + preConfirmationProperties, + ); + let txMeta: (TransactionMeta & Partial) | undefined; const isBridgeTx = isCrossChain( @@ -873,14 +882,23 @@ export class BridgeStatusController extends StaticIntervalPollingController - await this.#handleSolanaTx( - quoteResponse as QuoteResponse & QuoteMetadata, - ), - ); - this.#trackUnifiedSwapBridgeEvent( - UnifiedSwapBridgeEventName.SnapConfirmationViewed, - txMeta.id, + async () => { + try { + return await this.#handleSolanaTx( + quoteResponse as QuoteResponse & QuoteMetadata, + ); + } catch (error) { + this.#trackUnifiedSwapBridgeEvent( + UnifiedSwapBridgeEventName.Failed, + txMeta?.id, + { + error_message: (error as Error)?.message, + ...preConfirmationProperties, + }, + ); + throw error; + } + }, ); } // Submit EVM tx @@ -937,7 +955,9 @@ export class BridgeStatusController extends StaticIntervalPollingController await this.#handleEvmTransaction({ - transactionType: TransactionType.bridge, + transactionType: isBridgeTx + ? TransactionType.bridge + : TransactionType.swap, trade: quoteResponse.trade as TxData, quoteResponse, approvalTxId, @@ -948,7 +968,9 @@ export class BridgeStatusController extends StaticIntervalPollingController( eventName: T, - txMetaId: string, + txMetaId?: string, + eventProperties?: Pick[T], ) => { + if (!txMetaId) { + this.messagingSystem.call( + 'BridgeController:trackUnifiedSwapBridgeEvent', + eventName, + eventProperties ?? {}, + ); + return; + } + const historyItem: BridgeHistoryItem | undefined = this.state.txHistory[txMetaId]; if (!historyItem) { this.messagingSystem.call( 'BridgeController:trackUnifiedSwapBridgeEvent', eventName, - {}, + eventProperties ?? {}, ); return; } - let requiredEventProperties: Pick[T]; const selectedAccount = this.messagingSystem.call( 'AccountsController:getAccountByAddress', historyItem.account, ); - switch (eventName) { - case UnifiedSwapBridgeEventName.Submitted: - case UnifiedSwapBridgeEventName.Completed: - case UnifiedSwapBridgeEventName.Failed: - default: - requiredEventProperties = { - action_type: getActionType( - historyItem.quote.srcChainId, - historyItem.quote.destChainId, - ), - ...getRequestParamFromHistory(historyItem), - ...getRequestMetadataFromHistory(historyItem, selectedAccount), - ...getTradeDataFromHistory(historyItem), - ...getTxStatusesFromHistory(historyItem), - ...getFinalizedTxProperties(historyItem), - error_message: 'error_message', - price_impact: Number(historyItem.quote.priceData?.priceImpact ?? '0'), - }; - } + const requiredEventProperties = { + action_type: getActionType( + historyItem.quote.srcChainId, + historyItem.quote.destChainId, + ), + ...(eventProperties ?? {}), + ...getRequestParamFromHistory(historyItem), + ...getRequestMetadataFromHistory(historyItem, selectedAccount), + ...getTradeDataFromHistory(historyItem), + ...getTxStatusesFromHistory(historyItem), + ...getFinalizedTxProperties(historyItem), + ...getPriceImpactFromQuote(historyItem.quote), + }; this.messagingSystem.call( 'BridgeController:trackUnifiedSwapBridgeEvent', diff --git a/packages/bridge-status-controller/src/types.ts b/packages/bridge-status-controller/src/types.ts index 65cd837b903..3deb00280cc 100644 --- a/packages/bridge-status-controller/src/types.ts +++ b/packages/bridge-status-controller/src/types.ts @@ -18,7 +18,6 @@ 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, @@ -344,7 +343,6 @@ type AllowedActions = * The external events available to the BridgeStatusController. */ type AllowedEvents = - | MultichainTransactionsControllerTransactionConfirmedEvent | TransactionControllerTransactionFailedEvent | TransactionControllerTransactionConfirmedEvent; diff --git a/packages/bridge-status-controller/src/utils/metrics.test.ts b/packages/bridge-status-controller/src/utils/metrics.test.ts index 1a84196baf8..9a277259c97 100644 --- a/packages/bridge-status-controller/src/utils/metrics.test.ts +++ b/packages/bridge-status-controller/src/utils/metrics.test.ts @@ -1,4 +1,14 @@ import { StatusTypes, FeeType, ActionTypes } from '@metamask/bridge-controller'; +import { + MetricsSwapType, + MetricsActionType, +} from '@metamask/bridge-controller'; +import type { + TransactionMeta, + TransactionError, +} from '@metamask/transaction-controller'; +import { TransactionType } from '@metamask/transaction-controller'; +import { TransactionStatus } from '@metamask/transaction-controller'; import { getTxStatusesFromHistory, @@ -6,6 +16,7 @@ import { getRequestParamFromHistory, getTradeDataFromHistory, getRequestMetadataFromHistory, + getEVMTxPropertiesFromTransactionMeta, } from './metrics'; import type { BridgeHistoryItem } from '../types'; @@ -513,4 +524,110 @@ describe('metrics utils', () => { ); }); }); + + describe('getEVMSwapTxPropertiesFromTransactionMeta', () => { + const mockTransactionMeta: TransactionMeta = { + id: 'test-tx-id', + networkClientId: 'test-network', + status: 'submitted' as TransactionStatus, + time: 1234567890, + txParams: { + from: '0x123', + to: '0x456', + value: '0x0', + }, + chainId: '0x1', + sourceTokenSymbol: 'ETH', + destinationTokenSymbol: 'USDC', + sourceTokenAddress: '0x0000000000000000000000000000000000000000', + destinationTokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + type: TransactionType.swap, + }; + + it('should return correct properties for a successful swap transaction', () => { + const result = getEVMTxPropertiesFromTransactionMeta(mockTransactionMeta); + expect(result).toStrictEqual({ + error_message: undefined, + chain_id_source: 'eip155:1', + chain_id_destination: 'eip155:1', + token_symbol_source: 'ETH', + token_symbol_destination: 'USDC', + usd_amount_source: 100, + source_transaction: 'COMPLETE', + stx_enabled: false, + token_address_source: 'eip155:1/slip44:60', + token_address_destination: + 'eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + custom_slippage: false, + is_hardware_wallet: false, + swap_type: MetricsSwapType.SINGLE, + security_warnings: [], + price_impact: 0, + usd_quoted_gas: 0, + gas_included: false, + quoted_time_minutes: 0, + usd_quoted_return: 0, + provider: '', + actual_time_minutes: 0, + quote_vs_execution_ratio: 0, + quoted_vs_used_gas_ratio: 0, + usd_actual_return: 0, + usd_actual_gas: 0, + action_type: MetricsActionType.SWAPBRIDGE_V1, + }); + }); + + it('should handle failed transaction with error message', () => { + const failedTransactionMeta: TransactionMeta = { + ...mockTransactionMeta, + status: TransactionStatus.failed, + error: { + message: 'Transaction failed', + name: 'Error', + } as TransactionError, + }; + const result = getEVMTxPropertiesFromTransactionMeta( + failedTransactionMeta, + ); + expect(result.error_message).toBe('Failed to finalize swap tx'); + expect(result.source_transaction).toBe('FAILED'); + }); + + it('should handle missing token symbols', () => { + const noSymbolsTransactionMeta: TransactionMeta = { + ...mockTransactionMeta, + sourceTokenSymbol: undefined, + destinationTokenSymbol: undefined, + }; + const result = getEVMTxPropertiesFromTransactionMeta( + noSymbolsTransactionMeta, + ); + expect(result.token_symbol_source).toBe(''); + expect(result.token_symbol_destination).toBe(''); + }); + + it('should handle missing token addresses', () => { + const noAddressesTransactionMeta: TransactionMeta = { + ...mockTransactionMeta, + sourceTokenAddress: undefined, + destinationTokenAddress: undefined, + }; + const result = getEVMTxPropertiesFromTransactionMeta( + noAddressesTransactionMeta, + ); + expect(result.token_address_source).toBe('eip155:1/slip44:60'); + expect(result.token_address_destination).toBe('eip155:1/slip44:60'); + }); + + it('should handle crosschain swap type', () => { + const crosschainTransactionMeta: TransactionMeta = { + ...mockTransactionMeta, + type: TransactionType.swap, + }; + const result = getEVMTxPropertiesFromTransactionMeta( + crosschainTransactionMeta, + ); + expect(result.swap_type).toBe(MetricsSwapType.SINGLE); + }); + }); }); diff --git a/packages/bridge-status-controller/src/utils/metrics.ts b/packages/bridge-status-controller/src/utils/metrics.ts index e702855b3d5..4a7772cf62d 100644 --- a/packages/bridge-status-controller/src/utils/metrics.ts +++ b/packages/bridge-status-controller/src/utils/metrics.ts @@ -1,4 +1,9 @@ import type { AccountsControllerState } from '@metamask/accounts-controller'; +import type { + QuoteResponse, + TxData, + QuoteMetadata, +} from '@metamask/bridge-controller'; import { type TxStatusData, StatusTypes, @@ -12,9 +17,20 @@ import { isCustomSlippage, getSwapType, isHardwareWallet, + formatAddressToAssetId, + MetricsActionType, + MetricsSwapType, } from '@metamask/bridge-controller'; +import { + TransactionStatus, + TransactionType, + type TransactionMeta, +} from '@metamask/transaction-controller'; +import type { CaipAssetType } from '@metamask/utils'; import type { BridgeHistoryItem } from 'src/types'; +import type { QuoteFetchData } from '../../../bridge-controller/src/utils/metrics/types'; + export const getTxStatusesFromHistory = ({ status, hasApprovalTx, @@ -75,6 +91,26 @@ export const getRequestParamFromHistory = ( }; }; +export const getTradeDataFromQuote = ( + quoteResponse: QuoteResponse & QuoteMetadata, +): TradeData => { + return { + usd_quoted_gas: Number(quoteResponse.gasFee?.usd ?? 0), + gas_included: false, + provider: formatProviderLabel(quoteResponse.quote), + quoted_time_minutes: Number( + quoteResponse.estimatedProcessingTimeInSeconds / 60, + ), + usd_quoted_return: Number(quoteResponse.adjustedReturn?.usd ?? 0), + }; +}; + +export const getPriceImpactFromQuote = ( + quote: QuoteResponse['quote'], +): Pick => { + return { price_impact: Number(quote.priceData?.priceImpact ?? '0') }; +}; + export const getTradeDataFromHistory = ( historyItem: BridgeHistoryItem, ): TradeData => { @@ -105,3 +141,58 @@ export const getRequestMetadataFromHistory = ( security_warnings: [], }; }; + +/** + * Get the properties for a swap transaction that is not in the txHistory + * + * @param transactionMeta - The transaction meta + * @returns The properties for the swap transaction + */ +export const getEVMTxPropertiesFromTransactionMeta = ( + transactionMeta: TransactionMeta, +) => { + return { + source_transaction: + transactionMeta.status === TransactionStatus.failed + ? StatusTypes.FAILED + : StatusTypes.COMPLETE, + error_message: transactionMeta.error?.message + ? 'Failed to finalize swap tx' + : undefined, + chain_id_source: formatChainIdToCaip(transactionMeta.chainId), + chain_id_destination: formatChainIdToCaip(transactionMeta.chainId), + token_symbol_source: transactionMeta.sourceTokenSymbol ?? '', + token_symbol_destination: transactionMeta.destinationTokenSymbol ?? '', + usd_amount_source: 100, + stx_enabled: false, + token_address_source: + formatAddressToAssetId( + transactionMeta.sourceTokenAddress ?? '', + transactionMeta.chainId, + ) ?? ('' as CaipAssetType), + token_address_destination: + formatAddressToAssetId( + transactionMeta.destinationTokenAddress ?? '', + transactionMeta.chainId, + ) ?? ('' as CaipAssetType), + custom_slippage: false, + is_hardware_wallet: false, + swap_type: + transactionMeta.type === TransactionType.swap + ? MetricsSwapType.SINGLE + : MetricsSwapType.CROSSCHAIN, + security_warnings: [], + price_impact: 0, + usd_quoted_gas: 0, + gas_included: false, + quoted_time_minutes: 0, + usd_quoted_return: 0, + provider: '' as `${string}_${string}`, + actual_time_minutes: 0, + quote_vs_execution_ratio: 0, + quoted_vs_used_gas_ratio: 0, + usd_actual_return: 0, + usd_actual_gas: 0, + action_type: MetricsActionType.SWAPBRIDGE_V1, + }; +}; diff --git a/packages/bridge-status-controller/tsconfig.build.json b/packages/bridge-status-controller/tsconfig.build.json index bc350d54388..1ec93edefdf 100644 --- a/packages/bridge-status-controller/tsconfig.build.json +++ b/packages/bridge-status-controller/tsconfig.build.json @@ -11,7 +11,6 @@ { "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 2b313a0a49f..c281ec32f41 100644 --- a/packages/bridge-status-controller/tsconfig.json +++ b/packages/bridge-status-controller/tsconfig.json @@ -14,7 +14,6 @@ { "path": "../transaction-controller" }, { "path": "../gas-fee-controller" }, { "path": "../user-operation-controller" }, - { "path": "../multichain-transactions-controller" } ], "include": ["../../types", "./src"] } diff --git a/yarn.lock b/yarn.lock index 921c25a2c05..d881bbc8d76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2808,7 +2808,6 @@ __metadata: "@metamask/controller-utils": "npm:^11.10.0" "@metamask/gas-fee-controller": "npm:^24.0.0" "@metamask/keyring-api": "npm:^18.0.0" - "@metamask/multichain-transactions-controller": "npm:^3.0.0" "@metamask/network-controller": "npm:^24.0.0" "@metamask/polling-controller": "npm:^14.0.0" "@metamask/snaps-controllers": "npm:^12.3.1" @@ -2832,7 +2831,6 @@ __metadata: "@metamask/accounts-controller": ^31.0.0 "@metamask/bridge-controller": ^33.0.0 "@metamask/gas-fee-controller": ^24.0.0 - "@metamask/multichain-transactions-controller": ^3.0.0 "@metamask/network-controller": ^24.0.0 "@metamask/snaps-controllers": ^12.0.0 "@metamask/transaction-controller": ^58.0.0