From 65d188f692ff480b1668b1b2c6c108f4a1481341 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Fri, 27 Jun 2025 13:27:48 -0700 Subject: [PATCH 01/23] chore: wip batch --- .../src/bridge-status-controller.ts | 191 +++++++++++++----- .../src/utils/transaction.ts | 46 ++++- 2 files changed, 190 insertions(+), 47 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 6998d2a0a79..bb174bf4d17 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -32,6 +32,7 @@ import { type TransactionMeta, } from '@metamask/transaction-controller'; import type { UserOperationController } from '@metamask/user-operation-controller'; +import type { SignedTransaction } from '@metamask/utils'; import { numberToHex, type Hex } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; @@ -66,7 +67,9 @@ import { getEVMTxPropertiesFromTransactionMeta, getTxStatusesFromHistory, } from './utils/metrics'; +import type { SubmitSignedTransactionsFn } from './utils/transaction'; import { + createUnsignedTransactionsWithFees, getClientRequest, getKeyringRequest, getStatusRequestParams, @@ -115,6 +118,10 @@ export class BridgeStatusController extends StaticIntervalPollingController; @@ -133,6 +142,8 @@ export class BridgeStatusController extends StaticIntervalPollingController & QuoteMetadata; + quoteResponse: QuoteResponse & QuoteMetadata; approvalTxId?: string; requireApproval?: boolean; }) => { - return await this.#handleEvmTransaction({ - transactionType: isBridgeTx - ? TransactionType.bridge - : TransactionType.swap, - trade, - quoteResponse, - approvalTxId, - shouldWaitForHash: false, // Set to false to indicate we don't want to wait for hash + // TODO requireApproval param needs to be set + const { chainId, gasLimit, ...tradeWithoutChainId } = trade; + const approvalTx = { + ...approval, + chainId: numberToHex(chainId), + gas: gasLimit ? toHex(gasLimit) : '0x0', + from: trade.from ?? '', + gasLimit: gasLimit ? toHex(gasLimit) : '0x0', requireApproval, + }; + const unsignedTransactions = createUnsignedTransactionsWithFees( + [ + approvalTx, + { + ...tradeWithoutChainId, + chainId: numberToHex(chainId), + gas: gasLimit ? toHex(gasLimit) : '0x0', + from: trade.from ?? '', + }, + ], + { + maxFeePerGas: quoteResponse.quote.feeData.txFee?.maxFeePerGas ?? '0x0', + maxPriorityFeePerGas: + quoteResponse.quote.feeData.txFee?.maxPriorityFeePerGas ?? '0x0', + gas: gasLimit?.toString() ?? '0x0', + value: quoteResponse.trade?.value ?? '0x0', + }, + ); + console.error('====unsignedTransactions', unsignedTransactions); + const signedTransactions = (await this.#approveTransactionsWithSameNonceFn( + unsignedTransactions, + )) as never; + console.error('====signedTransactions', signedTransactions); + const networkClientId = this.messagingSystem.call( + 'NetworkController:findNetworkClientIdByChainId', + numberToHex(chainId), + ); + return await this.#submitSignedTransactionsFn({ + signedTransactions: signedTransactions as never, + txParams: { + ...trade, + chainId: numberToHex(chainId), + gasLimit: trade.gasLimit?.toString(), + gas: trade.gasLimit?.toString(), + }, + networkClientId, }); + // return await this.#handleEvmTransaction({ + // transactionType: isBridgeTx + // ? TransactionType.bridge + // : TransactionType.swap, + // trade, + // quoteResponse, + // approvalTxId, + // shouldWaitForHash: false, // Set to false to indicate we don't want to wait for hash + // requireApproval, + // }); }; /** @@ -909,44 +971,81 @@ export class BridgeStatusController extends StaticIntervalPollingController - isStxEnabledOnClient - ? await this.#handleEvmSmartTransaction({ - isBridgeTx, - trade: quoteResponse.trade as TxData, - quoteResponse, - approvalTxId, - requireApproval, - }) - : await this.#handleEvmTransaction({ - transactionType: isBridgeTx - ? TransactionType.bridge - : TransactionType.swap, - trade: quoteResponse.trade as TxData, - quoteResponse, - approvalTxId, - requireApproval, - }), - ); + async () => { + // Set approval time and id if an approval tx is needed + // const approvalTxMeta = await this.#handleApprovalTx( + // isBridgeTx, + // quoteResponse, + // requireApproval, + // ); + // const approvalTxMeta = await this.#handleEvmSmartTransaction({ + // isBridgeTx, + // trade: quoteResponse.approval as TxData, + // quoteResponse: quoteResponse as never, + // requireApproval, + // approvalTxId: undefined, + // }); + // console.error('====approvalTxMeta', approvalTxMeta); + const tradeMeta = await this.#handleEvmSmartTransaction({ + approval: quoteResponse.approval as TxData, + isBridgeTx, + trade: quoteResponse.trade as TxData, + quoteResponse: quoteResponse as never, + // approvalTxId: approvalTxMeta?.txHash ?? '', + requireApproval, + }); + console.error('====tradeMeta', tradeMeta); + return { tradeMeta }; + }, + ); + const { tradeMeta } = stxMeta; + // approvalTime = approvalTxMeta?.time; + // approvalTxId = approvalTxMeta?.id; + txMeta = { ...tradeMeta } as never; + } else { + // Set approval time and id if an approval tx is needed + const approvalTxMeta = await this.#handleApprovalTx( + isBridgeTx, + quoteResponse, + requireApproval, + ); + approvalTime = approvalTxMeta?.time; + approvalTxId = approvalTxMeta?.id; + // Handle smart transactions if enabled + txMeta = await this.#trace( + { + name: isBridgeTx + ? TraceName.BridgeTransactionCompleted + : TraceName.SwapTransactionCompleted, + data: { + srcChainId: formatChainIdToCaip(quoteResponse.quote.srcChainId), + stxEnabled: false, + }, + }, + async () => + await this.#handleEvmTransaction({ + transactionType: isBridgeTx + ? TransactionType.bridge + : TransactionType.swap, + trade: quoteResponse.trade as TxData, + quoteResponse, + approvalTxId, + requireApproval, + }), + ); + } } try { diff --git a/packages/bridge-status-controller/src/utils/transaction.ts b/packages/bridge-status-controller/src/utils/transaction.ts index 4a2b1a1f907..45e2f901869 100644 --- a/packages/bridge-status-controller/src/utils/transaction.ts +++ b/packages/bridge-status-controller/src/utils/transaction.ts @@ -8,12 +8,14 @@ import { type QuoteResponse, } from '@metamask/bridge-controller'; import { SolScope } from '@metamask/keyring-api'; +import type { TransactionParams } from '@metamask/transaction-controller'; import { TransactionStatus, TransactionType, type TransactionMeta, } from '@metamask/transaction-controller'; -import { createProjectLogger } from '@metamask/utils'; +import type { SignedTransaction } from '@metamask/utils'; +import { createProjectLogger, numberToHex } from '@metamask/utils'; import { v4 as uuid } from 'uuid'; import { LINEA_DELAY_MS } from '../constants'; @@ -193,3 +195,45 @@ export const getClientRequest = ( }, }; }; + +type TemporarySmartTransactionGasFees = { + maxFeePerGas: string; + maxPriorityFeePerGas: string; + gas: string; + value: string; +}; + +export const createUnsignedTransactionsWithFees = ( + unsignedTransactions: (Partial & { + chainId: string; + from: string; + })[], + fees: TemporarySmartTransactionGasFees, +) => { + const unsignedTransactionsWithFees = unsignedTransactions.map( + (unsignedTransaction) => { + const unsignedTransactionWithFees = { + ...unsignedTransaction, + maxFeePerGas: numberToHex(Number(fees.maxFeePerGas)), + maxPriorityFeePerGas: numberToHex(Number(fees.maxPriorityFeePerGas)), + gas: unsignedTransaction.gas, + value: unsignedTransaction.value, + }; + + return unsignedTransactionWithFees; + }, + ); + return unsignedTransactionsWithFees; +}; + +export type SubmitSignedTransactionsFn = ({ + transactionMeta, + txParams, + signedTransactions, + networkClientId, +}: { + signedTransactions: SignedTransaction[]; + transactionMeta?: TransactionMeta; + txParams?: TransactionParams; + networkClientId?: string; +}) => Promise<{ uuid: string; txHash: string; txHashes: string[] }>; From c84a683bc75dd06e0555c9cdb16a356fdd7d8d39 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Fri, 27 Jun 2025 13:27:59 -0700 Subject: [PATCH 02/23] Revert "chore: wip batch" This reverts commit 65d188f692ff480b1668b1b2c6c108f4a1481341. --- .../src/bridge-status-controller.ts | 191 +++++------------- .../src/utils/transaction.ts | 46 +---- 2 files changed, 47 insertions(+), 190 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index bb174bf4d17..6998d2a0a79 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -32,7 +32,6 @@ import { type TransactionMeta, } from '@metamask/transaction-controller'; import type { UserOperationController } from '@metamask/user-operation-controller'; -import type { SignedTransaction } from '@metamask/utils'; import { numberToHex, type Hex } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; @@ -67,9 +66,7 @@ import { getEVMTxPropertiesFromTransactionMeta, getTxStatusesFromHistory, } from './utils/metrics'; -import type { SubmitSignedTransactionsFn } from './utils/transaction'; import { - createUnsignedTransactionsWithFees, getClientRequest, getKeyringRequest, getStatusRequestParams, @@ -118,10 +115,6 @@ export class BridgeStatusController extends StaticIntervalPollingController; @@ -142,8 +133,6 @@ export class BridgeStatusController extends StaticIntervalPollingController & QuoteMetadata; approvalTxId?: string; requireApproval?: boolean; }) => { - // TODO requireApproval param needs to be set - const { chainId, gasLimit, ...tradeWithoutChainId } = trade; - const approvalTx = { - ...approval, - chainId: numberToHex(chainId), - gas: gasLimit ? toHex(gasLimit) : '0x0', - from: trade.from ?? '', - gasLimit: gasLimit ? toHex(gasLimit) : '0x0', + return await this.#handleEvmTransaction({ + transactionType: isBridgeTx + ? TransactionType.bridge + : TransactionType.swap, + trade, + quoteResponse, + approvalTxId, + shouldWaitForHash: false, // Set to false to indicate we don't want to wait for hash requireApproval, - }; - const unsignedTransactions = createUnsignedTransactionsWithFees( - [ - approvalTx, - { - ...tradeWithoutChainId, - chainId: numberToHex(chainId), - gas: gasLimit ? toHex(gasLimit) : '0x0', - from: trade.from ?? '', - }, - ], - { - maxFeePerGas: quoteResponse.quote.feeData.txFee?.maxFeePerGas ?? '0x0', - maxPriorityFeePerGas: - quoteResponse.quote.feeData.txFee?.maxPriorityFeePerGas ?? '0x0', - gas: gasLimit?.toString() ?? '0x0', - value: quoteResponse.trade?.value ?? '0x0', - }, - ); - console.error('====unsignedTransactions', unsignedTransactions); - const signedTransactions = (await this.#approveTransactionsWithSameNonceFn( - unsignedTransactions, - )) as never; - console.error('====signedTransactions', signedTransactions); - const networkClientId = this.messagingSystem.call( - 'NetworkController:findNetworkClientIdByChainId', - numberToHex(chainId), - ); - return await this.#submitSignedTransactionsFn({ - signedTransactions: signedTransactions as never, - txParams: { - ...trade, - chainId: numberToHex(chainId), - gasLimit: trade.gasLimit?.toString(), - gas: trade.gasLimit?.toString(), - }, - networkClientId, }); - // return await this.#handleEvmTransaction({ - // transactionType: isBridgeTx - // ? TransactionType.bridge - // : TransactionType.swap, - // trade, - // quoteResponse, - // approvalTxId, - // shouldWaitForHash: false, // Set to false to indicate we don't want to wait for hash - // requireApproval, - // }); }; /** @@ -971,81 +909,44 @@ export class BridgeStatusController extends StaticIntervalPollingController { - // Set approval time and id if an approval tx is needed - // const approvalTxMeta = await this.#handleApprovalTx( - // isBridgeTx, - // quoteResponse, - // requireApproval, - // ); - // const approvalTxMeta = await this.#handleEvmSmartTransaction({ - // isBridgeTx, - // trade: quoteResponse.approval as TxData, - // quoteResponse: quoteResponse as never, - // requireApproval, - // approvalTxId: undefined, - // }); - // console.error('====approvalTxMeta', approvalTxMeta); - const tradeMeta = await this.#handleEvmSmartTransaction({ - approval: quoteResponse.approval as TxData, - isBridgeTx, - trade: quoteResponse.trade as TxData, - quoteResponse: quoteResponse as never, - // approvalTxId: approvalTxMeta?.txHash ?? '', - requireApproval, - }); - console.error('====tradeMeta', tradeMeta); - return { tradeMeta }; - }, - ); - const { tradeMeta } = stxMeta; - // approvalTime = approvalTxMeta?.time; - // approvalTxId = approvalTxMeta?.id; - txMeta = { ...tradeMeta } as never; - } else { - // Set approval time and id if an approval tx is needed - const approvalTxMeta = await this.#handleApprovalTx( - isBridgeTx, - quoteResponse, - requireApproval, - ); - approvalTime = approvalTxMeta?.time; - approvalTxId = approvalTxMeta?.id; - // Handle smart transactions if enabled - txMeta = await this.#trace( - { - name: isBridgeTx - ? TraceName.BridgeTransactionCompleted - : TraceName.SwapTransactionCompleted, - data: { - srcChainId: formatChainIdToCaip(quoteResponse.quote.srcChainId), - stxEnabled: false, - }, + // Set approval time and id if an approval tx is needed + const approvalTxMeta = await this.#handleApprovalTx( + isBridgeTx, + quoteResponse, + requireApproval, + ); + approvalTime = approvalTxMeta?.time; + approvalTxId = approvalTxMeta?.id; + // Handle smart transactions if enabled + txMeta = await this.#trace( + { + name: isBridgeTx + ? TraceName.BridgeTransactionCompleted + : TraceName.SwapTransactionCompleted, + data: { + srcChainId: formatChainIdToCaip(quoteResponse.quote.srcChainId), + stxEnabled: isStxEnabledOnClient, }, - async () => - await this.#handleEvmTransaction({ - transactionType: isBridgeTx - ? TransactionType.bridge - : TransactionType.swap, - trade: quoteResponse.trade as TxData, - quoteResponse, - approvalTxId, - requireApproval, - }), - ); - } + }, + async () => + isStxEnabledOnClient + ? await this.#handleEvmSmartTransaction({ + isBridgeTx, + trade: quoteResponse.trade as TxData, + quoteResponse, + approvalTxId, + requireApproval, + }) + : await this.#handleEvmTransaction({ + transactionType: isBridgeTx + ? TransactionType.bridge + : TransactionType.swap, + trade: quoteResponse.trade as TxData, + quoteResponse, + approvalTxId, + requireApproval, + }), + ); } try { diff --git a/packages/bridge-status-controller/src/utils/transaction.ts b/packages/bridge-status-controller/src/utils/transaction.ts index 45e2f901869..4a2b1a1f907 100644 --- a/packages/bridge-status-controller/src/utils/transaction.ts +++ b/packages/bridge-status-controller/src/utils/transaction.ts @@ -8,14 +8,12 @@ import { type QuoteResponse, } from '@metamask/bridge-controller'; import { SolScope } from '@metamask/keyring-api'; -import type { TransactionParams } from '@metamask/transaction-controller'; import { TransactionStatus, TransactionType, type TransactionMeta, } from '@metamask/transaction-controller'; -import type { SignedTransaction } from '@metamask/utils'; -import { createProjectLogger, numberToHex } from '@metamask/utils'; +import { createProjectLogger } from '@metamask/utils'; import { v4 as uuid } from 'uuid'; import { LINEA_DELAY_MS } from '../constants'; @@ -195,45 +193,3 @@ export const getClientRequest = ( }, }; }; - -type TemporarySmartTransactionGasFees = { - maxFeePerGas: string; - maxPriorityFeePerGas: string; - gas: string; - value: string; -}; - -export const createUnsignedTransactionsWithFees = ( - unsignedTransactions: (Partial & { - chainId: string; - from: string; - })[], - fees: TemporarySmartTransactionGasFees, -) => { - const unsignedTransactionsWithFees = unsignedTransactions.map( - (unsignedTransaction) => { - const unsignedTransactionWithFees = { - ...unsignedTransaction, - maxFeePerGas: numberToHex(Number(fees.maxFeePerGas)), - maxPriorityFeePerGas: numberToHex(Number(fees.maxPriorityFeePerGas)), - gas: unsignedTransaction.gas, - value: unsignedTransaction.value, - }; - - return unsignedTransactionWithFees; - }, - ); - return unsignedTransactionsWithFees; -}; - -export type SubmitSignedTransactionsFn = ({ - transactionMeta, - txParams, - signedTransactions, - networkClientId, -}: { - signedTransactions: SignedTransaction[]; - transactionMeta?: TransactionMeta; - txParams?: TransactionParams; - networkClientId?: string; -}) => Promise<{ uuid: string; txHash: string; txHashes: string[] }>; From 429a51431d129bb3876aeaee17b50985ddce1e2d Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 30 Jun 2025 16:29:51 -0700 Subject: [PATCH 03/23] chore: selector for max button visibility --- packages/bridge-controller/src/index.ts | 1 + packages/bridge-controller/src/selectors.ts | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/packages/bridge-controller/src/index.ts b/packages/bridge-controller/src/index.ts index be436dd9b95..e1126071304 100644 --- a/packages/bridge-controller/src/index.ts +++ b/packages/bridge-controller/src/index.ts @@ -129,6 +129,7 @@ export { selectIsQuoteExpired, selectBridgeFeatureFlags, selectMinimumBalanceForRentExemptionInSOL, + selectMaxBalanceButtonVisibilityForSrcToken, } from './selectors'; export { DEFAULT_FEATURE_FLAG_CONFIG } from './constants/bridge'; diff --git a/packages/bridge-controller/src/selectors.ts b/packages/bridge-controller/src/selectors.ts index fd34152c39f..6f9de07f35b 100644 --- a/packages/bridge-controller/src/selectors.ts +++ b/packages/bridge-controller/src/selectors.ts @@ -25,6 +25,7 @@ import type { import { RequestStatus, SortOrder } from './types'; import { getNativeAssetForChainId, + isCrossChain, isNativeAddress, isSolanaChainId, } from './utils/bridge'; @@ -428,3 +429,23 @@ export const selectMinimumBalanceForRentExemptionInSOL = ( new BigNumber(state.minimumBalanceForRentExemptionInLamports ?? 0) .div(10 ** 9) .toString(); + +type MaxBalanceButtonVisibilityClientParams = { + isStxEnabled: boolean; +}; +export const selectMaxBalanceButtonVisibilityForSrcToken = createBridgeSelector( + [ + (state) => state.quoteRequest.srcTokenAddress, + (state) => state.quoteRequest.srcChainId, + (state) => state.quoteRequest.destChainId, + (_, { isStxEnabled }: MaxBalanceButtonVisibilityClientParams) => + isStxEnabled, + ], + + (srcTokenAddress, srcChainId, destChainId, isStxEnabled) => + Boolean( + srcChainId && + !isCrossChain(srcChainId, destChainId) && + (isNativeAddress(srcTokenAddress) ? isStxEnabled : true), + ), +); From 8eb8a069556132e537daeab87a067422767d5935 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 30 Jun 2025 18:29:14 -0700 Subject: [PATCH 04/23] refactor: extract gas fee calc and USDT reset --- .../src/bridge-status-controller.ts | 51 ------------------- .../bridge-status-controller/src/utils/gas.ts | 30 +++++++++++ .../src/utils/transaction.ts | 25 +++++++++ 3 files changed, 55 insertions(+), 51 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 6998d2a0a79..b089ae60086 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -777,57 +777,6 @@ export class BridgeStatusController extends StaticIntervalPollingController & QuoteMetadata, - ) => { - const hexChainId = formatChainIdToHex(quoteResponse.quote.srcChainId); - if ( - quoteResponse.approval && - isEthUsdt(hexChainId, quoteResponse.quote.srcAsset.address) - ) { - const allowance = new BigNumber( - await this.messagingSystem.call( - 'BridgeController:getBridgeERC20Allowance', - quoteResponse.quote.srcAsset.address, - hexChainId, - ), - ); - const shouldResetApproval = - allowance.lt(quoteResponse.sentAmount.amount) && allowance.gt(0); - if (shouldResetApproval) { - await this.#handleEvmTransaction({ - transactionType: TransactionType.bridgeApproval, - trade: { ...quoteResponse.approval, data: getEthUsdtResetData() }, - quoteResponse, - }); - } - } - }; - - readonly #calculateGasFees = async ( - transactionParams: TransactionParams, - networkClientId: string, - chainId: Hex, - ) => { - const { gasFeeEstimates } = this.messagingSystem.call( - 'GasFeeController:getState', - ); - const { estimates: txGasFeeEstimates } = await this.#estimateGasFeeFn({ - transactionParams, - chainId, - networkClientId, - }); - const { maxFeePerGas, maxPriorityFeePerGas } = getTxGasEstimates({ - networkGasFeeEstimates: gasFeeEstimates, - txGasFeeEstimates, - }); - const maxGasLimit = toHex(transactionParams.gas ?? 0); - - return { - maxFeePerGas, - maxPriorityFeePerGas, - gas: maxGasLimit, - }; }; /** diff --git a/packages/bridge-status-controller/src/utils/gas.ts b/packages/bridge-status-controller/src/utils/gas.ts index f3e91def1e0..1a12518d850 100644 --- a/packages/bridge-status-controller/src/utils/gas.ts +++ b/packages/bridge-status-controller/src/utils/gas.ts @@ -1,3 +1,4 @@ +import { toHex } from '@metamask/controller-utils'; import type { GasFeeEstimates, GasFeeState, @@ -5,8 +6,11 @@ import type { import type { FeeMarketGasFeeEstimates, TransactionController, + TransactionParams, } from '@metamask/transaction-controller'; +import type { Hex } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; +import type { BridgeStatusControllerMessenger } from 'src/types'; const getTransaction1559GasFeeEstimates = ( txGasFeeEstimates: FeeMarketGasFeeEstimates, @@ -50,3 +54,29 @@ export const getTxGasEstimates = ({ estimatedBaseFee, ); }; + +export const calculateGasFees = async ( + messagingSystem: BridgeStatusControllerMessenger, + estimateGasFeeFn: typeof TransactionController.prototype.estimateGasFee, + transactionParams: TransactionParams, + networkClientId: string, + chainId: Hex, +) => { + const { gasFeeEstimates } = messagingSystem.call('GasFeeController:getState'); + const { estimates: txGasFeeEstimates } = await estimateGasFeeFn({ + transactionParams, + chainId, + networkClientId, + }); + const { maxFeePerGas, maxPriorityFeePerGas } = getTxGasEstimates({ + networkGasFeeEstimates: gasFeeEstimates, + txGasFeeEstimates, + }); + const maxGasLimit = toHex(transactionParams.gas ?? 0); + + return { + maxFeePerGas, + maxPriorityFeePerGas, + gas: maxGasLimit, + }; +}; diff --git a/packages/bridge-status-controller/src/utils/transaction.ts b/packages/bridge-status-controller/src/utils/transaction.ts index 4a2b1a1f907..49bd77e65d6 100644 --- a/packages/bridge-status-controller/src/utils/transaction.ts +++ b/packages/bridge-status-controller/src/utils/transaction.ts @@ -21,6 +21,31 @@ import type { SolanaTransactionMeta } from '../types'; export const generateActionId = () => (Date.now() + Math.random()).toString(); +export const getUSDTAllowanceResetTx = async ( + messagingSystem: BridgeStatusControllerMessenger, + quoteResponse: QuoteResponse & QuoteMetadata, +) => { + const hexChainId = formatChainIdToHex(quoteResponse.quote.srcChainId); + if ( + quoteResponse.approval && + isEthUsdt(hexChainId, quoteResponse.quote.srcAsset.address) + ) { + const allowance = new BigNumber( + await messagingSystem.call( + 'BridgeController:getBridgeERC20Allowance', + quoteResponse.quote.srcAsset.address, + hexChainId, + ), + ); + const shouldResetApproval = + allowance.lt(quoteResponse.sentAmount.amount) && allowance.gt(0); + if (shouldResetApproval) { + return { ...quoteResponse.approval, data: getEthUsdtResetData() }; + } + } + return undefined; +}; + export const getStatusRequestParams = ( quoteResponse: QuoteResponse, ) => { From 05ce291515a207f8eac71c273092cafba97e7598 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 30 Jun 2025 18:32:17 -0700 Subject: [PATCH 05/23] fix: don't poll tx status before confirmation --- .../src/bridge-status-controller.ts | 73 ++++++++++++------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index b089ae60086..15808d63da0 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -212,6 +212,13 @@ export class BridgeStatusController extends StaticIntervalPollingController { // Check if we are already polling this tx, if so, skip restarting polling for that const srcTxMetaId = historyItem.txMetaId; + if (!srcTxMetaId) { + return false; + } const pollingToken = this.#pollingTokensByTxMetaId[srcTxMetaId]; return !pollingToken; }) @@ -281,11 +291,7 @@ export class BridgeStatusController extends StaticIntervalPollingController { const bridgeTxMetaId = historyItem.txMetaId; - // We manually call startPolling() here rather than go through startPollingForBridgeTxStatus() - // because we don't want to overwrite the existing historyItem in state - this.#pollingTokensByTxMetaId[bridgeTxMetaId] = this.startPolling({ - bridgeTxMetaId, - }); + this.startPollingForBridgeTxStatus(bridgeTxMetaId); }); }; @@ -302,12 +308,21 @@ export class BridgeStatusController extends StaticIntervalPollingController { - // Use the txMeta.id as the key so we can reference the txMeta in TransactionController - state.txHistory[bridgeTxMeta.id] = txHistoryItem; + // Use the txMeta.id or batchId as the key so we can reference the txMeta in TransactionController + state.txHistory[txIdToUse] = txHistoryItem; }); }; /** * Starts polling for the bridge tx status * - * @param txHistoryMeta - The parameters for creating the history item + * @param txId - The id of the tx to start polling for */ - startPollingForBridgeTxStatus = ( - txHistoryMeta: StartPollingForBridgeTxStatusArgsSerialized, - ) => { - const { quoteResponse, bridgeTxMeta } = txHistoryMeta; - - this.#addTxToHistory(txHistoryMeta); - - const isBridgeTx = isCrossChain( - quoteResponse.quote.srcChainId, - quoteResponse.quote.destChainId, - ); - if (isBridgeTx) { - this.#pollingTokensByTxMetaId[bridgeTxMeta.id] = this.startPolling({ - bridgeTxMetaId: bridgeTxMeta.id, + startPollingForBridgeTxStatus = (txId?: string) => { + if (!txId) { + return; + } + const txHistoryMeta = this.state.txHistory[txId]; + if (!txHistoryMeta) { + return; + } + const { quote, txMetaId, type } = txHistoryMeta; + + const isBridgeTx = + type === TransactionType.bridge || + isCrossChain(quote.srcChainId, quote.destChainId); + if (isBridgeTx && txMetaId) { + this.#pollingTokensByTxMetaId[txMetaId] = this.startPolling({ + bridgeTxMetaId: txMetaId, }); } }; @@ -899,9 +916,9 @@ export class BridgeStatusController extends StaticIntervalPollingController Date: Mon, 30 Jun 2025 18:34:07 -0700 Subject: [PATCH 06/23] feat: evm tx batching --- .../src/bridge-status-controller.ts | 282 +++++++++--------- .../bridge-status-controller/src/types.ts | 12 +- .../src/utils/transaction.ts | 180 ++++++++++- 3 files changed, 331 insertions(+), 143 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 15808d63da0..99f8a7ac9ad 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -7,8 +7,6 @@ import type { } from '@metamask/bridge-controller'; import { formatChainIdToHex, - getEthUsdtResetData, - isEthUsdt, isSolanaChainId, StatusTypes, UnifiedSwapBridgeEventName, @@ -19,13 +17,9 @@ import { isHardwareWallet, } from '@metamask/bridge-controller'; import type { TraceCallback } from '@metamask/controller-utils'; -import { toHex } from '@metamask/controller-utils'; import { EthAccountType, SolScope } from '@metamask/keyring-api'; import { StaticIntervalPollingController } from '@metamask/polling-controller'; -import type { - TransactionController, - TransactionParams, -} from '@metamask/transaction-controller'; +import type { TransactionController } from '@metamask/transaction-controller'; import { TransactionStatus, TransactionType, @@ -33,7 +27,6 @@ import { } from '@metamask/transaction-controller'; import type { UserOperationController } from '@metamask/user-operation-controller'; import { numberToHex, type Hex } from '@metamask/utils'; -import { BigNumber } from 'bignumber.js'; import { BRIDGE_PROD_API_BASE_URL, @@ -55,7 +48,6 @@ import { fetchBridgeTxStatus, getStatusRequestWithSrcTxHash, } from './utils/bridge-status'; -import { getTxGasEstimates } from './utils/gas'; import { getFinalizedTxProperties, getPriceImpactFromQuote, @@ -67,14 +59,16 @@ import { getTxStatusesFromHistory, } from './utils/metrics'; import { + getAddTransactionBatchParams, + getAddTransactionParams, getClientRequest, getKeyringRequest, getStatusRequestParams, getTxMetaFields, + getUSDTAllowanceResetTx, handleLineaDelay, handleSolanaTxResponse, } from './utils/transaction'; -import { generateActionId } from './utils/transaction'; const metadata: StateMetadata = { // We want to persist the bridge status state so that we can show the proper data for the Activity list @@ -109,6 +103,8 @@ export class BridgeStatusController extends StaticIntervalPollingController { - const { type, id } = transactionMeta; - if (type === TransactionType.swap) { + // This is not published for approvals so assume that it's a trade + const { type, id, batchId } = transactionMeta; + // Re-key history item by txId when tx is confirmed + this.update((bridgeState) => { + if (batchId && bridgeState.txHistory[batchId]) { + bridgeState.txHistory[id] = bridgeState.txHistory[batchId]; + bridgeState.txHistory[id].txMetaId = id; + delete bridgeState.txHistory[batchId]; + } + }); + + if ( + type === TransactionType.swap || + this.state.txHistory[id].type === TransactionType.swap + ) { this.#trackUnifiedSwapBridgeEvent( UnifiedSwapBridgeEventName.Completed, id, @@ -616,72 +628,6 @@ export class BridgeStatusController extends StaticIntervalPollingController & QuoteMetadata, - requireApproval = false, - ): Promise => { - const { approval } = quoteResponse; - - if (approval) { - const approveTx = async () => { - await this.#handleUSDTAllowanceReset(quoteResponse); - - const approvalTxMeta = await this.#handleEvmTransaction({ - transactionType: isBridgeTx - ? TransactionType.bridgeApproval - : TransactionType.swapApproval, - trade: approval, - quoteResponse, - requireApproval, - }); - - await handleLineaDelay(quoteResponse); - return approvalTxMeta; - }; - - return await this.#trace( - { - name: isBridgeTx - ? TraceName.BridgeTransactionApprovalCompleted - : TraceName.SwapTransactionApprovalCompleted, - data: { - srcChainId: formatChainIdToCaip(quoteResponse.quote.srcChainId), - stxEnabled: false, - }, - }, - approveTx, - ); - } - - return undefined; - }; - - readonly #handleEvmSmartTransaction = async ({ - isBridgeTx, - trade, - quoteResponse, - approvalTxId, - requireApproval = false, - }: { - isBridgeTx: boolean; - trade: TxData; - quoteResponse: Omit & QuoteMetadata; - approvalTxId?: string; - requireApproval?: boolean; - }) => { - return await this.#handleEvmTransaction({ - transactionType: isBridgeTx - ? TransactionType.bridge - : TransactionType.swap, - trade, - quoteResponse, - approvalTxId, - shouldWaitForHash: false, // Set to false to indicate we don't want to wait for hash - requireApproval, - }); - }; - /** * Submits an EVM transaction to the TransactionController * @@ -709,7 +655,13 @@ export class BridgeStatusController extends StaticIntervalPollingController => { - const actionId = generateActionId().toString(); + let result: + | Awaited>['result'] + | Awaited< + ReturnType + >['hash'] + | undefined; + let transactionMeta: TransactionMeta | undefined; const selectedAccount = this.messagingSystem.call( 'AccountsController:getAccountByAddress', @@ -721,42 +673,17 @@ export class BridgeStatusController extends StaticIntervalPollingController[0] = { - ...trade, - chainId: hexChainId, - gasLimit: trade.gasLimit?.toString(), - gas: trade.gasLimit?.toString(), - }; - const transactionParamsWithMaxGas: TransactionParams = { - ...transactionParams, - ...(await this.#calculateGasFees( - transactionParams, - networkClientId, + const { transactionParamsWithMaxGas, requestOptions } = + await getAddTransactionParams({ hexChainId, - )), - }; - - let result: - | Awaited>['result'] - | Awaited< - ReturnType - >['hash'] - | undefined; - let transactionMeta: TransactionMeta | undefined; + messagingSystem: this.messagingSystem, + estimateGasFeeFn: this.#estimateGasFeeFn, + transactionType, + trade, + quoteResponse, + requireApproval, + }); const isSmartContractAccount = selectedAccount.type === EthAccountType.Erc4337; @@ -794,6 +721,75 @@ export class BridgeStatusController extends StaticIntervalPollingController { + let approvalTime: number | undefined, approvalTxId: string | undefined; + const { approval, trade } = quoteResponse; + const resetApproval = approval + ? await getUSDTAllowanceResetTx(this.messagingSystem, quoteResponse) + : undefined; + + const commonParams = { + quoteResponse, + requireApproval, + shouldWaitForHash: true, + }; + if (approval) { + const approvalParams = { + ...commonParams, + transactionType: isBridgeTx + ? TransactionType.bridgeApproval + : TransactionType.swapApproval, + }; + const resetApprovalTxMeta = resetApproval + ? await this.#handleEvmTransaction({ + trade: resetApproval, + ...approvalParams, + }) + : undefined; + const approvalTxMeta = quoteResponse.approval + ? await this.#handleEvmTransaction({ + trade: approval, + ...approvalParams, + }) + : undefined; + approvalTime = resetApprovalTxMeta?.time ?? approvalTxMeta?.time; + approvalTxId = resetApprovalTxMeta?.id ?? approvalTxMeta?.id; + await handleLineaDelay(quoteResponse); + } + const txMeta = await this.#handleEvmTransaction({ + ...commonParams, + trade, + approvalTxId, + transactionType: isBridgeTx + ? TransactionType.bridge + : TransactionType.swap, + }); + return { + tradeMeta: txMeta, + time: approvalTime, + approvalId: approvalTxId, + }; + }; + + readonly #handleEvmTransactionBatch = async ( + args: Omit< + Parameters[0], + 'messagingSystem' + >, + ) => { + const transactionParams = await getAddTransactionBatchParams({ + messagingSystem: this.messagingSystem, + ...args, + }); + return await this.#addTransactionBatchFn(transactionParams); }; /** @@ -806,7 +802,7 @@ export class BridgeStatusController extends StaticIntervalPollingController & QuoteMetadata, isStxEnabledOnClient: boolean, - ): Promise> => { + ): Promise & Partial> => { this.messagingSystem.call('BridgeController:stopPollingForQuotes'); // Before the tx is confirmed, its data is not available in txHistory @@ -826,7 +822,7 @@ export class BridgeStatusController extends StaticIntervalPollingController; + let txMeta: Partial & Partial; let approvalTime: number | undefined, approvalTxId: string | undefined; const isBridgeTx = isCrossChain( @@ -875,14 +871,6 @@ export class BridgeStatusController extends StaticIntervalPollingController - isStxEnabledOnClient - ? await this.#handleEvmSmartTransaction({ + async () => { + const { approval, trade } = quoteResponse as QuoteResponse; + const resetApproval = approval + ? await getUSDTAllowanceResetTx(this.messagingSystem, quoteResponse) + : undefined; + if (isStxEnabledOnClient) { + if (quoteResponse.approval) { + return await this.#handleEvmTransactionBatch({ isBridgeTx, - trade: quoteResponse.trade as TxData, - quoteResponse, - approvalTxId, - requireApproval, - }) - : await this.#handleEvmTransaction({ - transactionType: isBridgeTx - ? TransactionType.bridge - : TransactionType.swap, - trade: quoteResponse.trade as TxData, + trade, + approval, + resetApproval, quoteResponse, - approvalTxId, requireApproval, - }), + }); + } + return await this.#handleEvmTransaction({ + trade, + quoteResponse, + requireApproval, + shouldWaitForHash: false, + transactionType: isBridgeTx + ? TransactionType.bridge + : TransactionType.swap, + }); + } + const { tradeMeta, time, approvalId } = + await this.#handleEvmTransactions({ + quoteResponse: quoteResponse as QuoteResponse & QuoteMetadata, + isBridgeTx, + requireApproval, + }); + approvalTime = time; + approvalTxId = approvalId; + return tradeMeta; + }, ); } diff --git a/packages/bridge-status-controller/src/types.ts b/packages/bridge-status-controller/src/types.ts index ea4ab91637c..0fb07309630 100644 --- a/packages/bridge-status-controller/src/types.ts +++ b/packages/bridge-status-controller/src/types.ts @@ -29,7 +29,9 @@ import type { TransactionControllerGetStateAction, TransactionControllerTransactionConfirmedEvent, TransactionControllerTransactionFailedEvent, + TransactionControllerTransactionFinishedEvent, TransactionMeta, + TransactionType, } from '@metamask/transaction-controller'; import type { BridgeStatusController } from './bridge-status-controller'; @@ -104,7 +106,9 @@ export type StatusResponse = Infer; export type RefuelStatusResponse = object & StatusResponse; export type BridgeHistoryItem = { - txMetaId: string; // Need this to handle STX that might not have a txHash immediately + txMetaId?: string; // Need this to handle STX that might not have a txHash immediately + batchId?: string; // Need this to handle STX that might not have a txHash or txMetaId immediately + type?: TransactionType; // Batched txs don't always have an accurate type after confirmation so this type is saved at the time of submission quote: Quote; status: StatusResponse; startTime?: number; // timestamp in ms @@ -176,7 +180,8 @@ export type QuoteMetadataSerialized = { }; export type StartPollingForBridgeTxStatusArgs = { - bridgeTxMeta: TransactionMeta; + type?: TransactionType; + bridgeTxMeta: Partial; statusRequest: StatusRequest; quoteResponse: QuoteResponse & QuoteMetadata; startTime?: BridgeHistoryItem['startTime']; @@ -269,7 +274,8 @@ type AllowedActions = */ type AllowedEvents = | TransactionControllerTransactionFailedEvent - | TransactionControllerTransactionConfirmedEvent; + | TransactionControllerTransactionConfirmedEvent + | TransactionControllerTransactionFinishedEvent; /** * The messenger for the BridgeStatusController. diff --git a/packages/bridge-status-controller/src/utils/transaction.ts b/packages/bridge-status-controller/src/utils/transaction.ts index 49bd77e65d6..e7a6c0f107e 100644 --- a/packages/bridge-status-controller/src/utils/transaction.ts +++ b/packages/bridge-status-controller/src/utils/transaction.ts @@ -1,23 +1,39 @@ import type { AccountsControllerState } from '@metamask/accounts-controller'; -import type { TxData } from '@metamask/bridge-controller'; +import type { Quote, TxData } from '@metamask/bridge-controller'; import { ChainId, + FeeType, formatChainIdToHex, + getEthUsdtResetData, isCrossChain, + isEthUsdt, type QuoteMetadata, type QuoteResponse, } from '@metamask/bridge-controller'; +import { toHex } from '@metamask/controller-utils'; import { SolScope } from '@metamask/keyring-api'; +import type { TransactionController } from '@metamask/transaction-controller'; import { TransactionStatus, TransactionType, + type BatchTransactionParams, type TransactionMeta, } from '@metamask/transaction-controller'; +import type { Hex } from '@metamask/utils'; import { createProjectLogger } from '@metamask/utils'; +import { BigNumber } from 'bignumber.js'; import { v4 as uuid } from 'uuid'; +import { calculateGasFees } from './gas'; +import type { + TransactionBatchSingleRequest, + TransactionParams, +} from '../../../transaction-controller/src/types'; import { LINEA_DELAY_MS } from '../constants'; -import type { SolanaTransactionMeta } from '../types'; +import type { + BridgeStatusControllerMessenger, + SolanaTransactionMeta, +} from '../types'; export const generateActionId = () => (Date.now() + Math.random()).toString(); @@ -218,3 +234,163 @@ export const getClientRequest = ( }, }; }; + +export const toTransactionBatchParams = ( + { chainId, gasLimit, ...trade }: TxData, + { txFee }: Quote['feeData'], +): BatchTransactionParams => { + return { + ...trade, + gas: toHex(gasLimit ?? 0), + data: trade.data as `0x${string}`, + to: trade.to as `0x${string}`, + value: trade.value as `0x${string}`, + maxFeePerGas: txFee ? toHex(txFee.maxFeePerGas ?? 0) : undefined, + maxPriorityFeePerGas: txFee + ? toHex(txFee.maxPriorityFeePerGas ?? 0) + : undefined, + }; +}; + +export const getAddTransactionBatchParams = async ({ + messagingSystem, + isBridgeTx, + approval, + resetApproval, + trade, + quoteResponse, + requireApproval = false, +}: { + messagingSystem: BridgeStatusControllerMessenger; + isBridgeTx: boolean; + approval?: TxData; + resetApproval?: TxData; + trade: TxData; + quoteResponse: Omit & QuoteMetadata; + requireApproval?: boolean; +}) => { + const selectedAccount = messagingSystem.call( + 'AccountsController:getAccountByAddress', + trade.from, + ); + if (!selectedAccount) { + throw new Error( + 'Failed to submit cross-chain swap batch transaction: unknown account in trade data', + ); + } + const hexChainId = formatChainIdToHex(trade.chainId); + const networkClientId = messagingSystem.call( + 'NetworkController:findNetworkClientIdByChainId', + hexChainId, + ); + + const transactions: TransactionBatchSingleRequest[] = []; + if (resetApproval) { + transactions.push({ + type: isBridgeTx + ? TransactionType.bridgeApproval + : TransactionType.swapApproval, + params: toTransactionBatchParams( + resetApproval, + quoteResponse.quote.feeData, + ), + }); + } + if (approval) { + transactions.push({ + type: isBridgeTx + ? TransactionType.bridgeApproval + : TransactionType.swapApproval, + params: toTransactionBatchParams(approval, quoteResponse.quote.feeData), + }); + } + transactions.push({ + type: isBridgeTx ? TransactionType.bridge : TransactionType.swap, + params: toTransactionBatchParams(trade, quoteResponse.quote.feeData), + }); + const transactionParams: Parameters< + TransactionController['addTransactionBatch'] + >[0] = { + // disable7702: true, // TODO enable if chain supports 7702 + networkClientId, + requireApproval, + origin: 'metamask', + from: trade.from as `0x${string}`, + transactions, + }; + + // const isSmartContractAccount = + // selectedAccount.type === EthAccountType.Erc4337; + // if (isSmartContractAccount && this.#addUserOperationFromTransactionFn) { + // const smartAccountTxResult = + // await this.#addUserOperationFromTransactionFn( + // transactionParamsWithMaxGas, + // requestOptions, + // ); + // result = smartAccountTxResult.transactionHash; + // transactionMeta = { + // ...requestOptions, + // chainId: hexChainId, + // txParams: transactionParamsWithMaxGas, + // time: Date.now(), + // id: smartAccountTxResult.id, + // status: TransactionStatus.confirmed, + // }; + // } + + return transactionParams; +}; + +export const getAddTransactionParams = async ({ + messagingSystem, + estimateGasFeeFn, + transactionType, + trade, + quoteResponse, + hexChainId, + requireApproval = false, +}: { + messagingSystem: BridgeStatusControllerMessenger; + estimateGasFeeFn: typeof TransactionController.prototype.estimateGasFee; + transactionType: TransactionType; + trade: TxData; + quoteResponse: Omit & QuoteMetadata; + hexChainId: Hex; + requireApproval?: boolean; +}) => { + const actionId = generateActionId().toString(); + const networkClientId = messagingSystem.call( + 'NetworkController:findNetworkClientIdByChainId', + hexChainId, + ); + + const requestOptions = { + actionId, + networkClientId, + requireApproval, + transactionType, + origin: 'metamask', + }; + const transactionParams: Parameters< + TransactionController['addTransaction'] + >[0] = { + ...trade, + chainId: hexChainId, + gasLimit: trade.gasLimit?.toString(), + gas: trade.gasLimit?.toString(), + }; + const transactionParamsWithMaxGas: TransactionParams = { + ...transactionParams, + ...(quoteResponse.quote.feeData[FeeType.TX_FEE] + ? toTransactionBatchParams(trade, quoteResponse.quote.feeData) + : await calculateGasFees( + messagingSystem, + estimateGasFeeFn, + transactionParams, + networkClientId, + hexChainId, + )), + }; + + return { transactionParamsWithMaxGas, requestOptions }; +}; From 2b515b9a20f7c6d76e830db7bf7c42aaf8afd445 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 30 Jun 2025 18:53:13 -0700 Subject: [PATCH 07/23] chore: update changelogs --- packages/bridge-controller/CHANGELOG.md | 4 ++++ packages/bridge-status-controller/CHANGELOG.md | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 2f313c427da..30f3e8c694d 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Export `selectMaxBalanceButtonVisibilityForSrcToken` which returns whether a native asset is eligible for gas-included swaps ([#6051](https://github.com/MetaMask/core/pull/6051)) + ## [34.0.0] ### Added diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 8b7f006c826..3d77aa6645b 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `batchId` to BridgeHistoryItem to enable querying history by batchId when txId is not defined ([#6051](https://github.com/MetaMask/core/pull/6051)) +- Add optional `type` to BridgeHistoryItem to indicate the TransactionType (bridge or swap). This mitigates an edge case in which batched STX lose their TransactionType after confirmation ([#6051](https://github.com/MetaMask/core/pull/6051)) + +### Changed + +- **BREAKING** Add tx batching functionality, which requires an `addTransactionBatchFn` handler to be passed to the BridgeStatusController's constructor ([#6051](https://github.com/MetaMask/core/pull/6051)) +- **BREAKING** Make BridgeHistoryItem.txMetaId optional. Batched transactions don't immediately have a transaction ID so a transaction may be keyed using batchId until it is confirmed ([#6051](https://github.com/MetaMask/core/pull/6051)) + +### Fixed + +- Wait until a bridge transaction is confirmed before polling for its status. This reduces (or fully removes) premature `getTxStatus` calls, and enables adding batched bridge txs to history before its transaction Id is available ([#6051](https://github.com/MetaMask/core/pull/6051)) + ## [33.0.0] ### Changed From 8c70ea76398957999d3041f504ab20b5ed6276bb Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 30 Jun 2025 19:05:55 -0700 Subject: [PATCH 08/23] Revert "chore: selector for max button visibility" This reverts commit 429a51431d129bb3876aeaee17b50985ddce1e2d. --- packages/bridge-controller/CHANGELOG.md | 4 ---- packages/bridge-controller/src/index.ts | 1 - packages/bridge-controller/src/selectors.ts | 21 --------------------- 3 files changed, 26 deletions(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 30f3e8c694d..2f313c427da 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -7,10 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Added - -- Export `selectMaxBalanceButtonVisibilityForSrcToken` which returns whether a native asset is eligible for gas-included swaps ([#6051](https://github.com/MetaMask/core/pull/6051)) - ## [34.0.0] ### Added diff --git a/packages/bridge-controller/src/index.ts b/packages/bridge-controller/src/index.ts index e1126071304..be436dd9b95 100644 --- a/packages/bridge-controller/src/index.ts +++ b/packages/bridge-controller/src/index.ts @@ -129,7 +129,6 @@ export { selectIsQuoteExpired, selectBridgeFeatureFlags, selectMinimumBalanceForRentExemptionInSOL, - selectMaxBalanceButtonVisibilityForSrcToken, } from './selectors'; export { DEFAULT_FEATURE_FLAG_CONFIG } from './constants/bridge'; diff --git a/packages/bridge-controller/src/selectors.ts b/packages/bridge-controller/src/selectors.ts index 6f9de07f35b..fd34152c39f 100644 --- a/packages/bridge-controller/src/selectors.ts +++ b/packages/bridge-controller/src/selectors.ts @@ -25,7 +25,6 @@ import type { import { RequestStatus, SortOrder } from './types'; import { getNativeAssetForChainId, - isCrossChain, isNativeAddress, isSolanaChainId, } from './utils/bridge'; @@ -429,23 +428,3 @@ export const selectMinimumBalanceForRentExemptionInSOL = ( new BigNumber(state.minimumBalanceForRentExemptionInLamports ?? 0) .div(10 ** 9) .toString(); - -type MaxBalanceButtonVisibilityClientParams = { - isStxEnabled: boolean; -}; -export const selectMaxBalanceButtonVisibilityForSrcToken = createBridgeSelector( - [ - (state) => state.quoteRequest.srcTokenAddress, - (state) => state.quoteRequest.srcChainId, - (state) => state.quoteRequest.destChainId, - (_, { isStxEnabled }: MaxBalanceButtonVisibilityClientParams) => - isStxEnabled, - ], - - (srcTokenAddress, srcChainId, destChainId, isStxEnabled) => - Boolean( - srcChainId && - !isCrossChain(srcChainId, destChainId) && - (isNativeAddress(srcTokenAddress) ? isStxEnabled : true), - ), -); From ba0d02577a89e4917103a49123480017b249e708 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 30 Jun 2025 19:08:02 -0700 Subject: [PATCH 09/23] wip test fix --- .../src/bridge-status-controller.test.ts | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index b1eaae022d8..3dc1cb0a62c 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable jest/no-conditional-in-test */ /* eslint-disable jest/no-restricted-matchers */ import type { AccountsControllerActions } from '@metamask/accounts-controller'; import { Messenger } from '@metamask/base-controller'; @@ -556,13 +555,17 @@ const executePollingWithPendingStatus = async () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), + addTransactionBatchFn: jest.fn(), config: {}, + state: { + txHistory: {}, + }, }); const startPollingSpy = jest.spyOn(bridgeStatusController, 'startPolling'); // Execution bridgeStatusController.startPollingForBridgeTxStatus( - getMockStartPollingForBridgeTxStatusArgs(), + getMockStartPollingForBridgeTxStatusArgs().bridgeTxMeta.id, ); fetchBridgeTxStatusSpy.mockImplementationOnce(async () => { return MockStatusResponse.getPending(); @@ -593,6 +596,7 @@ const mockSelectedAccount = { const addTransactionFn = jest.fn(); const estimateGasFeeFn = jest.fn(); const addUserOperationFromTransactionFn = jest.fn(); +const addTransactionBatchFn = jest.fn(); const getController = (call: jest.Mock, traceFn?: jest.Mock) => { const controller = new BridgeStatusController({ @@ -608,6 +612,7 @@ const getController = (call: jest.Mock, traceFn?: jest.Mock) => { addTransactionFn, estimateGasFeeFn, addUserOperationFromTransactionFn, + addTransactionBatchFn, traceFn, }); @@ -637,6 +642,7 @@ describe('BridgeStatusController', () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), + addTransactionBatchFn: jest.fn(), }); expect(bridgeStatusController.state).toStrictEqual(EMPTY_INIT_STATE); expect(mockMessengerSubscribe.mock.calls).toMatchSnapshot(); @@ -650,14 +656,12 @@ describe('BridgeStatusController', () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), - state: { - txHistory: MockTxHistory.getPending(), - }, + addTransactionBatchFn: jest.fn(), }); // Execution bridgeStatusController.startPollingForBridgeTxStatus( - getMockStartPollingForBridgeTxStatusArgs(), + getMockStartPollingForBridgeTxStatusArgs().bridgeTxMeta.id, ); // Assertion @@ -686,6 +690,7 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), + addTransactionBatchFn: jest.fn(), }); jest.advanceTimersByTime(10000); await flushPromises(); @@ -695,6 +700,7 @@ describe('BridgeStatusController', () => { }); }); + /* describe('startPollingForBridgeTxStatus', () => { beforeEach(() => { jest.clearAllMocks(); @@ -709,6 +715,10 @@ describe('BridgeStatusController', () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), + addTransactionBatchFn: jest.fn(), + state: { + txHistory: MockTxHistory.getPending(), + }, }); // Execution @@ -747,6 +757,7 @@ describe('BridgeStatusController', () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), + addTransactionBatchFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest.spyOn( bridgeStatusUtils, @@ -828,6 +839,7 @@ describe('BridgeStatusController', () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), + addTransactionBatchFn: jest.fn(), }); // Start polling with args that have no srcTxHash @@ -908,6 +920,7 @@ describe('BridgeStatusController', () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), + addTransactionBatchFn: jest.fn(), }); // Execution @@ -971,6 +984,7 @@ describe('BridgeStatusController', () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), + addTransactionBatchFn: jest.fn(), }); // Start polling with no srcTxHash @@ -998,6 +1012,7 @@ describe('BridgeStatusController', () => { jest.restoreAllMocks(); }); }); + */ describe('resetState', () => { it('resets the state', async () => { @@ -1014,6 +1029,7 @@ describe('BridgeStatusController', () => { }); }); + /* describe('wipeBridgeStatus', () => { it('wipes the bridge status for the given address', async () => { // Setup @@ -1059,6 +1075,7 @@ describe('BridgeStatusController', () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), + addTransactionBatchFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') @@ -1146,6 +1163,7 @@ describe('BridgeStatusController', () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), + addTransactionBatchFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') @@ -1247,6 +1265,7 @@ describe('BridgeStatusController', () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), + addTransactionBatchFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') @@ -1316,6 +1335,7 @@ describe('BridgeStatusController', () => { expect(txHistoryItems[0].quote.destChainId).toBe(123); }); }); + */ describe('submitTx: Solana bridge', () => { const mockQuoteResponse: QuoteResponse & QuoteMetadata = { @@ -1780,7 +1800,7 @@ describe('BridgeStatusController', () => { expect(startPollingForBridgeTxStatusSpy).not.toHaveBeenCalled(); }); }); - + /* describe('submitTx: EVM bridge', () => { const mockEvmQuoteResponse = { ...getMockQuote(), @@ -2474,6 +2494,7 @@ describe('BridgeStatusController', () => { expect(addUserOperationFromTransactionFn.mock.calls).toMatchSnapshot(); }); }); + */ describe('subscription handlers', () => { let mockBridgeStatusMessenger: jest.Mocked; @@ -2539,6 +2560,7 @@ describe('BridgeStatusController', () => { addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addUserOperationFromTransactionFn: jest.fn(), + addTransactionBatchFn: jest.fn(), state: { txHistory: { ...MockTxHistory.getPending(), From 5d3b4491866757e475e480652c5f9d2ef2bf20d7 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 12:25:06 -0700 Subject: [PATCH 10/23] fix: disable 7702 --- .../src/bridge-status-controller.ts | 248 +++++++++++------- .../bridge-status-controller/src/utils/gas.ts | 4 + .../src/utils/transaction.ts | 45 +++- 3 files changed, 192 insertions(+), 105 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 99f8a7ac9ad..844f626e6ac 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -191,6 +191,8 @@ export class BridgeStatusController extends StaticIntervalPollingController { - // This is not published for approvals so assume that it's a trade - const { type, id, batchId } = transactionMeta; - // Re-key history item by txId when tx is confirmed - this.update((bridgeState) => { - if (batchId && bridgeState.txHistory[batchId]) { - bridgeState.txHistory[id] = bridgeState.txHistory[batchId]; - bridgeState.txHistory[id].txMetaId = id; - delete bridgeState.txHistory[batchId]; - } - }); + const { type, id } = transactionMeta; + this.#updateTxHistoryWithTxMeta(transactionMeta); + + console.error('=====transactionConfirmed', transactionMeta); if ( type === TransactionType.swap || - this.state.txHistory[id].type === TransactionType.swap + this.state.txHistory[id]?.type === TransactionType.swap ) { + // TODO update status to COMPLETED and completion time this.#trackUnifiedSwapBridgeEvent( UnifiedSwapBridgeEventName.Completed, id, @@ -227,7 +224,7 @@ export class BridgeStatusController extends StaticIntervalPollingController { + const { id, batchId, type, status } = transactionMeta; + + // TODO if txMeta is failed, stop polling and update item + const txHistoryItemById = id ? this.state.txHistory[id] : undefined; + const txHistoryItemByBatchId = batchId + ? this.state.txHistory[batchId] + : undefined; + if ( + status === TransactionStatus.failed && + (txHistoryItemById || txHistoryItemByBatchId) + ) { + const pollingToken = this.#pollingTokensByTxMetaId[id]; + if (pollingToken) { + this.stopPollingByPollingToken(pollingToken); + } + if (txHistoryItemById) { + this.update((state) => { + state.txHistory[id].status = { + ...txHistoryItemById.status, + status: StatusTypes.FAILED, + }; + }); + } + if (txHistoryItemByBatchId && batchId) { + this.update((state) => { + state.txHistory[batchId].status = { + ...txHistoryItemByBatchId.status, + status: StatusTypes.FAILED, + }; + }); + } + } + // if tx batchId is in history, update the history item with the new transactionMeta + if (batchId && txHistoryItemByBatchId) { + this.update((state) => { + state.txHistory[id] = state.txHistory[batchId]; + state.txHistory[id].txMetaId = id; + delete state.txHistory[batchId]; + }); + } + if ( + txHistoryItemById && + batchId === txHistoryItemById.batchId && + type && + [ + TransactionType.swapApproval, + TransactionType.bridgeApproval, + TransactionType.tokenMethodApprove, + ].includes(type) + ) { + // TODO if tx is approval, append to either batchId or id as approvalTxId + this.update((state) => { + state.txHistory[id].approvalTxId = id; + }); + } + }; + resetState = () => { this.update((state) => { state.txHistory = DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE.txHistory; @@ -485,7 +543,7 @@ export class BridgeStatusController extends StaticIntervalPollingController { - let approvalTime: number | undefined, approvalTxId: string | undefined; - const { approval, trade } = quoteResponse; - const resetApproval = approval - ? await getUSDTAllowanceResetTx(this.messagingSystem, quoteResponse) - : undefined; - - const commonParams = { - quoteResponse, - requireApproval, - shouldWaitForHash: true, - }; - if (approval) { - const approvalParams = { - ...commonParams, - transactionType: isBridgeTx - ? TransactionType.bridgeApproval - : TransactionType.swapApproval, - }; - const resetApprovalTxMeta = resetApproval - ? await this.#handleEvmTransaction({ - trade: resetApproval, - ...approvalParams, - }) - : undefined; - const approvalTxMeta = quoteResponse.approval - ? await this.#handleEvmTransaction({ - trade: approval, - ...approvalParams, - }) - : undefined; - approvalTime = resetApprovalTxMeta?.time ?? approvalTxMeta?.time; - approvalTxId = resetApprovalTxMeta?.id ?? approvalTxMeta?.id; - await handleLineaDelay(quoteResponse); - } - const txMeta = await this.#handleEvmTransaction({ - ...commonParams, - trade, - approvalTxId, - transactionType: isBridgeTx - ? TransactionType.bridge - : TransactionType.swap, - }); - return { - tradeMeta: txMeta, - time: approvalTime, - approvalId: approvalTxId, - }; - }; + // readonly #handleEvmTransactions = async ({ + // quoteResponse, + // isBridgeTx, + // requireApproval, + // }: { + // quoteResponse: QuoteResponse & QuoteMetadata; + // isBridgeTx: boolean; + // requireApproval: boolean; + // }) => { + // let approvalTime: number | undefined, approvalTxId: string | undefined; + // const { approval, trade } = quoteResponse; + // const resetApproval = approval + // ? await getUSDTAllowanceResetTx(this.messagingSystem, quoteResponse) + // : undefined; + + // const commonParams = { + // quoteResponse, + // requireApproval, + // shouldWaitForHash: true, + // }; + // if (approval) { + // const approvalParams = { + // ...commonParams, + // transactionType: isBridgeTx + // ? TransactionType.bridgeApproval + // : TransactionType.swapApproval, + // }; + // const resetApprovalTxMeta = resetApproval + // ? await this.#handleEvmTransaction({ + // trade: resetApproval, + // ...approvalParams, + // }) + // : undefined; + // const approvalTxMeta = quoteResponse.approval + // ? await this.#handleEvmTransaction({ + // trade: approval, + // ...approvalParams, + // }) + // : undefined; + // approvalTime = resetApprovalTxMeta?.time ?? approvalTxMeta?.time; + // approvalTxId = resetApprovalTxMeta?.id ?? approvalTxMeta?.id; + // await handleLineaDelay(quoteResponse); + // } + // const txMeta = await this.#handleEvmTransaction({ + // ...commonParams, + // trade, + // approvalTxId, + // transactionType: isBridgeTx + // ? TransactionType.bridge + // : TransactionType.swap, + // }); + // return { + // tradeMeta: txMeta, + // time: approvalTime, + // approvalId: approvalTxId, + // }; + // }; readonly #handleEvmTransactionBatch = async ( args: Omit< @@ -887,36 +945,36 @@ export class BridgeStatusController extends StaticIntervalPollingController { + if (disable7702) { + return {}; + } const { gasFeeEstimates } = messagingSystem.call('GasFeeController:getState'); const { estimates: txGasFeeEstimates } = await estimateGasFeeFn({ transactionParams, diff --git a/packages/bridge-status-controller/src/utils/transaction.ts b/packages/bridge-status-controller/src/utils/transaction.ts index e7a6c0f107e..36e43b637d5 100644 --- a/packages/bridge-status-controller/src/utils/transaction.ts +++ b/packages/bridge-status-controller/src/utils/transaction.ts @@ -236,20 +236,27 @@ export const getClientRequest = ( }; export const toTransactionBatchParams = ( + disable7702: boolean, { chainId, gasLimit, ...trade }: TxData, { txFee }: Quote['feeData'], ): BatchTransactionParams => { - return { + const params = { ...trade, - gas: toHex(gasLimit ?? 0), data: trade.data as `0x${string}`, to: trade.to as `0x${string}`, value: trade.value as `0x${string}`, - maxFeePerGas: txFee ? toHex(txFee.maxFeePerGas ?? 0) : undefined, - maxPriorityFeePerGas: txFee - ? toHex(txFee.maxPriorityFeePerGas ?? 0) - : undefined, }; + if (disable7702) { + return { + ...params, + gas: toHex(gasLimit ?? 0), + maxFeePerGas: txFee ? toHex(txFee.maxFeePerGas ?? 0) : undefined, + maxPriorityFeePerGas: txFee + ? toHex(txFee.maxPriorityFeePerGas ?? 0) + : undefined, + }; + } + return params; }; export const getAddTransactionBatchParams = async ({ @@ -284,6 +291,8 @@ export const getAddTransactionBatchParams = async ({ hexChainId, ); + // 7702 enables gasless txs for smart accounts, so we disable it for now + const disable7702 = true; const transactions: TransactionBatchSingleRequest[] = []; if (resetApproval) { transactions.push({ @@ -291,7 +300,9 @@ export const getAddTransactionBatchParams = async ({ ? TransactionType.bridgeApproval : TransactionType.swapApproval, params: toTransactionBatchParams( + disable7702, resetApproval, + quoteResponse.quote.feeData, ), }); @@ -301,17 +312,25 @@ export const getAddTransactionBatchParams = async ({ type: isBridgeTx ? TransactionType.bridgeApproval : TransactionType.swapApproval, - params: toTransactionBatchParams(approval, quoteResponse.quote.feeData), + params: toTransactionBatchParams( + disable7702, + approval, + quoteResponse.quote.feeData, + ), }); } transactions.push({ type: isBridgeTx ? TransactionType.bridge : TransactionType.swap, - params: toTransactionBatchParams(trade, quoteResponse.quote.feeData), + params: toTransactionBatchParams( + disable7702, + trade, + quoteResponse.quote.feeData, + ), }); const transactionParams: Parameters< TransactionController['addTransactionBatch'] >[0] = { - // disable7702: true, // TODO enable if chain supports 7702 + disable7702, networkClientId, requireApproval, origin: 'metamask', @@ -379,11 +398,17 @@ export const getAddTransactionParams = async ({ gasLimit: trade.gasLimit?.toString(), gas: trade.gasLimit?.toString(), }; + const disable7702 = true; const transactionParamsWithMaxGas: TransactionParams = { ...transactionParams, ...(quoteResponse.quote.feeData[FeeType.TX_FEE] - ? toTransactionBatchParams(trade, quoteResponse.quote.feeData) + ? toTransactionBatchParams( + disable7702, + trade, + quoteResponse.quote.feeData, + ) : await calculateGasFees( + disable7702, messagingSystem, estimateGasFeeFn, transactionParams, From 62285f2066bfbccfcb56ee2d43d848a3eafe687b Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 13:52:14 -0700 Subject: [PATCH 11/23] fix: preserve transaction type of batched transactions --- packages/transaction-controller/CHANGELOG.md | 4 + .../src/utils/batch.test.ts | 104 ++++++++++++++++++ .../transaction-controller/src/utils/batch.ts | 14 ++- 3 files changed, 118 insertions(+), 4 deletions(-) diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 1e189e0e80d..faec546da35 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Preserve type of nested transactions in batch if specified in `TransactionBatchSingleRequest` ([]()) + ## [58.1.0] ### Added diff --git a/packages/transaction-controller/src/utils/batch.test.ts b/packages/transaction-controller/src/utils/batch.test.ts index 0f484dd0f15..54f97c3e93d 100644 --- a/packages/transaction-controller/src/utils/batch.test.ts +++ b/packages/transaction-controller/src/utils/batch.test.ts @@ -834,6 +834,110 @@ describe('Batch Utils', () => { }); }); + it('calls publish batch hook with requested transaction type', async () => { + const publishBatchHook: jest.MockedFn = jest.fn(); + + addTransactionMock + .mockResolvedValueOnce({ + transactionMeta: { + ...TRANSACTION_META_MOCK, + id: TRANSACTION_ID_MOCK, + }, + result: Promise.resolve(''), + }) + .mockResolvedValueOnce({ + transactionMeta: { + ...TRANSACTION_META_MOCK, + id: TRANSACTION_ID_2_MOCK, + }, + result: Promise.resolve(''), + }); + + publishBatchHook.mockResolvedValue({ + results: [ + { + transactionHash: TRANSACTION_HASH_MOCK, + }, + { + transactionHash: TRANSACTION_HASH_2_MOCK, + }, + ], + }); + + addTransactionBatch({ + ...request, + publishBatchHook, + request: { + ...request.request, + transactions: [ + { + ...request.request.transactions[0], + type: TransactionType.swap, + }, + { + ...request.request.transactions[1], + type: TransactionType.bridge, + }, + ], + disable7702: true, + }, + }).catch(() => { + // Intentionally empty + }); + + await flushPromises(); + + const publishHooks = addTransactionMock.mock.calls.map( + ([, options]) => options.publishHook, + ); + + publishHooks[0]?.( + TRANSACTION_META_MOCK, + TRANSACTION_SIGNATURE_MOCK, + ).catch(() => { + // Intentionally empty + }); + + publishHooks[1]?.( + TRANSACTION_META_MOCK, + TRANSACTION_SIGNATURE_2_MOCK, + ).catch(() => { + // Intentionally empty + }); + + await flushPromises(); + + expect(publishBatchHook).toHaveBeenCalledTimes(1); + expect(publishBatchHook).toHaveBeenCalledWith({ + from: FROM_MOCK, + networkClientId: NETWORK_CLIENT_ID_MOCK, + transactions: [ + { + id: TRANSACTION_ID_MOCK, + params: { + ...TRANSACTION_BATCH_PARAMS_MOCK, + gas: undefined, + maxFeePerGas: undefined, + maxPriorityFeePerGas: undefined, + }, + signedTx: TRANSACTION_SIGNATURE_MOCK, + type: TransactionType.swap, + }, + { + id: TRANSACTION_ID_2_MOCK, + params: { + ...TRANSACTION_BATCH_PARAMS_MOCK, + gas: undefined, + maxFeePerGas: undefined, + maxPriorityFeePerGas: undefined, + }, + signedTx: TRANSACTION_SIGNATURE_2_MOCK, + type: TransactionType.bridge, + }, + ], + }); + }); + it('resolves individual publish hooks with transaction hashes from publish batch hook', async () => { const publishBatchHook: jest.MockedFn = jest.fn(); diff --git a/packages/transaction-controller/src/utils/batch.ts b/packages/transaction-controller/src/utils/batch.ts index 53d489662f5..84665ab34c5 100644 --- a/packages/transaction-controller/src/utils/batch.ts +++ b/packages/transaction-controller/src/utils/batch.ts @@ -246,7 +246,7 @@ async function getNestedTransactionMeta( ethQuery: EthQuery, ): Promise { const { from } = request; - const { params } = singleRequest; + const { params, type: requestType } = singleRequest; const { type } = await determineTransactionType( { from, ...params }, @@ -255,7 +255,7 @@ async function getNestedTransactionMeta( return { ...params, - type, + type: requestType ?? type, }; } @@ -544,7 +544,7 @@ async function processTransactionWithHook( request: AddTransactionBatchRequest, txBatchMeta?: TransactionBatchMeta, ) { - const { existingTransaction, params } = nestedTransaction; + const { existingTransaction, params, type } = nestedTransaction; const { addTransaction, @@ -595,6 +595,7 @@ async function processTransactionWithHook( const { transactionMeta } = await addTransaction( transactionMetaForGasEstimates.txParams, { + type, batchId, disableGasBuffer: true, networkClientId, @@ -620,11 +621,16 @@ async function processTransactionWithHook( value, }; - log('Processed new transaction with hook', { id, params: newParams }); + log('Processed new transaction with hook', { + id, + params: newParams, + type: nestedTransaction.type, + }); return { id, params: newParams, + type: nestedTransaction.type, }; } From 5d66e31d9ab2283e1d0fe29ca70d6a1b27a4c585 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 14:24:10 -0700 Subject: [PATCH 12/23] chore: remove UserOperationController tx submission --- .../bridge-status-controller/package.json | 1 - .../src/bridge-status-controller.ts | 51 +++---------------- .../tsconfig.build.json | 3 +- .../bridge-status-controller/tsconfig.json | 3 +- yarn.lock | 3 +- 5 files changed, 10 insertions(+), 51 deletions(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 622705b544f..62dc5b74b54 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -52,7 +52,6 @@ "@metamask/keyring-api": "^18.0.0", "@metamask/polling-controller": "^14.0.0", "@metamask/superstruct": "^3.1.0", - "@metamask/user-operation-controller": "^37.0.0", "@metamask/utils": "^11.2.0", "bignumber.js": "^9.1.2", "uuid": "^8.3.2" diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 844f626e6ac..f89a89d4311 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -25,7 +25,6 @@ import { TransactionType, type TransactionMeta, } from '@metamask/transaction-controller'; -import type { UserOperationController } from '@metamask/user-operation-controller'; import { numberToHex, type Hex } from '@metamask/utils'; import { @@ -107,8 +106,6 @@ export class BridgeStatusController extends StaticIntervalPollingController>['result'] - | Awaited< - ReturnType - >['hash'], + hashPromise?: Awaited< + ReturnType + >['result'], ): Promise => { const transactionHash = await hashPromise; const finalTransactionMeta: TransactionMeta | undefined = @@ -713,14 +705,6 @@ export class BridgeStatusController extends StaticIntervalPollingController => { - let result: - | Awaited>['result'] - | Awaited< - ReturnType - >['hash'] - | undefined; - let transactionMeta: TransactionMeta | undefined; - const selectedAccount = this.messagingSystem.call( 'AccountsController:getAccountByAddress', trade.from, @@ -743,31 +727,10 @@ export class BridgeStatusController extends StaticIntervalPollingController Date: Tue, 1 Jul 2025 15:41:40 -0700 Subject: [PATCH 13/23] chore: always use batched EVM txs --- .../src/bridge-status-controller.ts | 217 ++---------------- .../bridge-status-controller/src/types.ts | 7 +- .../bridge-status-controller/src/utils/gas.ts | 17 +- .../src/utils/transaction.ts | 170 +++++--------- 4 files changed, 95 insertions(+), 316 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index f89a89d4311..a470aaad12f 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -6,7 +6,6 @@ import type { QuoteResponse, } from '@metamask/bridge-controller'; import { - formatChainIdToHex, isSolanaChainId, StatusTypes, UnifiedSwapBridgeEventName, @@ -17,7 +16,7 @@ import { isHardwareWallet, } from '@metamask/bridge-controller'; import type { TraceCallback } from '@metamask/controller-utils'; -import { EthAccountType, SolScope } from '@metamask/keyring-api'; +import { SolScope } from '@metamask/keyring-api'; import { StaticIntervalPollingController } from '@metamask/polling-controller'; import type { TransactionController } from '@metamask/transaction-controller'; import { @@ -59,13 +58,10 @@ import { } from './utils/metrics'; import { getAddTransactionBatchParams, - getAddTransactionParams, getClientRequest, getKeyringRequest, getStatusRequestParams, - getTxMetaFields, getUSDTAllowanceResetTx, - handleLineaDelay, handleSolanaTxResponse, } from './utils/transaction'; @@ -100,8 +96,6 @@ export class BridgeStatusController extends StaticIntervalPollingController; clientId: BridgeClientId; fetchFn: FetchFunction; - addTransactionFn: typeof TransactionController.prototype.addTransaction; estimateGasFeeFn: typeof TransactionController.prototype.estimateGasFee; addTransactionBatchFn: typeof TransactionController.prototype.addTransactionBatch; config?: { @@ -144,7 +136,6 @@ export class BridgeStatusController extends StaticIntervalPollingController { + console.error('=====transactionFailed', transactionMeta); const { type, status, id } = transactionMeta; if ( type && @@ -204,10 +196,7 @@ export class BridgeStatusController extends StaticIntervalPollingController - >['result'], - ): Promise => { - const transactionHash = await hashPromise; - const finalTransactionMeta: TransactionMeta | undefined = - this.messagingSystem - .call('TransactionController:getState') - .transactions.find( - (tx: TransactionMeta) => tx.hash === transactionHash, - ); - if (!finalTransactionMeta) { - throw new Error( - 'Failed to submit cross-chain swap tx: txMeta for txHash was not found', - ); - } - return finalTransactionMeta; - }; - /** - * Submits an EVM transaction to the TransactionController + * Submits EVM transactions to the TransactionController * - * @param params - The parameters for the transaction - * @param params.transactionType - The type of transaction to submit - * @param params.trade - The trade data to confirm - * @param params.quoteResponse - The quote response - * @param params.approvalTxId - The tx id of the approval tx - * @param params.shouldWaitForHash - Whether to wait for the hash of the transaction - * @param params.requireApproval - Whether to require approval for the transaction - * @returns The transaction meta + * @param args - The parameters for the transaction + * @param args.isBridgeTx - Whether the transaction is a bridge transaction + * @param args.trade - The trade data to confirm + * @param args.approval - The approval data to confirm + * @param args.resetApproval - The ethereum:USDT reset approval data to confirm + * @param args.quoteResponse - The quote response + * @param args.requireApproval - Whether to require approval for the transaction + * @returns A partial TransactionMeta that contains the batchId */ - readonly #handleEvmTransaction = async ({ - transactionType, - trade, - quoteResponse, - approvalTxId, - shouldWaitForHash = true, - requireApproval = false, - }: { - transactionType: TransactionType; - trade: TxData; - quoteResponse: Omit & QuoteMetadata; - approvalTxId?: string; - shouldWaitForHash?: boolean; - requireApproval?: boolean; - }): Promise => { - const selectedAccount = this.messagingSystem.call( - 'AccountsController:getAccountByAddress', - trade.from, - ); - if (!selectedAccount) { - throw new Error( - 'Failed to submit cross-chain swap transaction: unknown account in trade data', - ); - } - const hexChainId = formatChainIdToHex(trade.chainId); - - const { transactionParamsWithMaxGas, requestOptions } = - await getAddTransactionParams({ - hexChainId, - messagingSystem: this.messagingSystem, - estimateGasFeeFn: this.#estimateGasFeeFn, - transactionType, - trade, - quoteResponse, - requireApproval, - }); - - const { result, transactionMeta } = await this.#addTransactionFn( - transactionParamsWithMaxGas, - requestOptions, - ); - - if (shouldWaitForHash) { - return await this.#waitForHashAndReturnFinalTxMeta(result); - } - - return { - ...getTxMetaFields(quoteResponse, approvalTxId), - ...transactionMeta, - }; - }; - - // readonly #handleEvmTransactions = async ({ - // quoteResponse, - // isBridgeTx, - // requireApproval, - // }: { - // quoteResponse: QuoteResponse & QuoteMetadata; - // isBridgeTx: boolean; - // requireApproval: boolean; - // }) => { - // let approvalTime: number | undefined, approvalTxId: string | undefined; - // const { approval, trade } = quoteResponse; - // const resetApproval = approval - // ? await getUSDTAllowanceResetTx(this.messagingSystem, quoteResponse) - // : undefined; - - // const commonParams = { - // quoteResponse, - // requireApproval, - // shouldWaitForHash: true, - // }; - // if (approval) { - // const approvalParams = { - // ...commonParams, - // transactionType: isBridgeTx - // ? TransactionType.bridgeApproval - // : TransactionType.swapApproval, - // }; - // const resetApprovalTxMeta = resetApproval - // ? await this.#handleEvmTransaction({ - // trade: resetApproval, - // ...approvalParams, - // }) - // : undefined; - // const approvalTxMeta = quoteResponse.approval - // ? await this.#handleEvmTransaction({ - // trade: approval, - // ...approvalParams, - // }) - // : undefined; - // approvalTime = resetApprovalTxMeta?.time ?? approvalTxMeta?.time; - // approvalTxId = resetApprovalTxMeta?.id ?? approvalTxMeta?.id; - // await handleLineaDelay(quoteResponse); - // } - // const txMeta = await this.#handleEvmTransaction({ - // ...commonParams, - // trade, - // approvalTxId, - // transactionType: isBridgeTx - // ? TransactionType.bridge - // : TransactionType.swap, - // }); - // return { - // tradeMeta: txMeta, - // time: approvalTime, - // approvalId: approvalTxId, - // }; - // }; - readonly #handleEvmTransactionBatch = async ( args: Omit< Parameters[0], - 'messagingSystem' + 'messagingSystem' | 'estimateGasFeeFn' >, ) => { const transactionParams = await getAddTransactionBatchParams({ messagingSystem: this.messagingSystem, + estimateGasFeeFn: this.#estimateGasFeeFn, ...args, }); return await this.#addTransactionBatchFn(transactionParams); @@ -844,12 +697,12 @@ export class BridgeStatusController extends StaticIntervalPollingController & Partial; - let approvalTime: number | undefined, approvalTxId: string | undefined; const isBridgeTx = isCrossChain( quoteResponse.quote.srcChainId, quoteResponse.quote.destChainId, ); + const startTime = Date.now(); // Submit SOLANA tx if ( @@ -908,36 +761,14 @@ export class BridgeStatusController extends StaticIntervalPollingController; statusRequest: StatusRequest; quoteResponse: QuoteResponse & QuoteMetadata; @@ -274,8 +270,7 @@ type AllowedActions = */ type AllowedEvents = | TransactionControllerTransactionFailedEvent - | TransactionControllerTransactionConfirmedEvent - | TransactionControllerTransactionFinishedEvent; + | TransactionControllerTransactionConfirmedEvent; /** * The messenger for the BridgeStatusController. diff --git a/packages/bridge-status-controller/src/utils/gas.ts b/packages/bridge-status-controller/src/utils/gas.ts index e041e17d8b6..35a978f80e7 100644 --- a/packages/bridge-status-controller/src/utils/gas.ts +++ b/packages/bridge-status-controller/src/utils/gas.ts @@ -1,3 +1,4 @@ +import type { TxData } from '@metamask/bridge-controller'; import { toHex } from '@metamask/controller-utils'; import type { GasFeeEstimates, @@ -6,7 +7,6 @@ import type { import type { FeeMarketGasFeeEstimates, TransactionController, - TransactionParams, } from '@metamask/transaction-controller'; import type { Hex } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; @@ -59,13 +59,24 @@ export const calculateGasFees = async ( disable7702: boolean, messagingSystem: BridgeStatusControllerMessenger, estimateGasFeeFn: typeof TransactionController.prototype.estimateGasFee, - transactionParams: TransactionParams, + { chainId: _, gasLimit, ...trade }: TxData, networkClientId: string, chainId: Hex, + txFee?: { maxFeePerGas: string; maxPriorityFeePerGas: string }, ) => { - if (disable7702) { + if (!disable7702) { return {}; } + if (txFee) { + return { ...txFee, gas: gasLimit?.toString() }; + } + const transactionParams = { + ...trade, + gas: gasLimit?.toString(), + data: trade.data as `0x${string}`, + to: trade.to as `0x${string}`, + value: trade.value as `0x${string}`, + }; const { gasFeeEstimates } = messagingSystem.call('GasFeeController:getState'); const { estimates: txGasFeeEstimates } = await estimateGasFeeFn({ transactionParams, diff --git a/packages/bridge-status-controller/src/utils/transaction.ts b/packages/bridge-status-controller/src/utils/transaction.ts index 36e43b637d5..f1877198c8c 100644 --- a/packages/bridge-status-controller/src/utils/transaction.ts +++ b/packages/bridge-status-controller/src/utils/transaction.ts @@ -1,8 +1,7 @@ import type { AccountsControllerState } from '@metamask/accounts-controller'; -import type { Quote, TxData } from '@metamask/bridge-controller'; import { ChainId, - FeeType, + type TxData, formatChainIdToHex, getEthUsdtResetData, isCrossChain, @@ -19,16 +18,12 @@ import { type BatchTransactionParams, type TransactionMeta, } from '@metamask/transaction-controller'; -import type { Hex } from '@metamask/utils'; import { createProjectLogger } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; import { v4 as uuid } from 'uuid'; import { calculateGasFees } from './gas'; -import type { - TransactionBatchSingleRequest, - TransactionParams, -} from '../../../transaction-controller/src/types'; +import type { TransactionBatchSingleRequest } from '../../../transaction-controller/src/types'; import { LINEA_DELAY_MS } from '../constants'; import type { BridgeStatusControllerMessenger, @@ -235,28 +230,31 @@ export const getClientRequest = ( }; }; -export const toTransactionBatchParams = ( +export const toTransactionBatchParams = async ( disable7702: boolean, { chainId, gasLimit, ...trade }: TxData, - { txFee }: Quote['feeData'], -): BatchTransactionParams => { + { + maxFeePerGas, + maxPriorityFeePerGas, + gas, + }: { maxFeePerGas?: string; maxPriorityFeePerGas?: string; gas?: string }, +): Promise => { const params = { ...trade, data: trade.data as `0x${string}`, to: trade.to as `0x${string}`, value: trade.value as `0x${string}`, }; - if (disable7702) { - return { - ...params, - gas: toHex(gasLimit ?? 0), - maxFeePerGas: txFee ? toHex(txFee.maxFeePerGas ?? 0) : undefined, - maxPriorityFeePerGas: txFee - ? toHex(txFee.maxPriorityFeePerGas ?? 0) - : undefined, - }; + if (!disable7702) { + return params; } - return params; + + return { + ...params, + gas: toHex(gas ?? 0), + maxFeePerGas: toHex(maxFeePerGas ?? 0), + maxPriorityFeePerGas: toHex(maxPriorityFeePerGas ?? 0), + }; }; export const getAddTransactionBatchParams = async ({ @@ -265,8 +263,14 @@ export const getAddTransactionBatchParams = async ({ approval, resetApproval, trade, - quoteResponse, + quoteResponse: { + quote: { + feeData: { txFee }, + gasIncluded, + }, + }, requireApproval = false, + estimateGasFeeFn, }: { messagingSystem: BridgeStatusControllerMessenger; isBridgeTx: boolean; @@ -275,6 +279,7 @@ export const getAddTransactionBatchParams = async ({ trade: TxData; quoteResponse: Omit & QuoteMetadata; requireApproval?: boolean; + estimateGasFeeFn: typeof TransactionController.prototype.estimateGasFee; }) => { const selectedAccount = messagingSystem.call( 'AccountsController:getAccountByAddress', @@ -295,37 +300,55 @@ export const getAddTransactionBatchParams = async ({ const disable7702 = true; const transactions: TransactionBatchSingleRequest[] = []; if (resetApproval) { + const gasFees = await calculateGasFees( + disable7702, + messagingSystem, + estimateGasFeeFn, + resetApproval, + networkClientId, + hexChainId, + gasIncluded ? txFee : undefined, + ); transactions.push({ type: isBridgeTx ? TransactionType.bridgeApproval : TransactionType.swapApproval, - params: toTransactionBatchParams( + params: await toTransactionBatchParams( disable7702, resetApproval, - - quoteResponse.quote.feeData, + gasFees, ), }); } if (approval) { + const gasFees = await calculateGasFees( + disable7702, + messagingSystem, + estimateGasFeeFn, + approval, + networkClientId, + hexChainId, + gasIncluded ? txFee : undefined, + ); transactions.push({ type: isBridgeTx ? TransactionType.bridgeApproval : TransactionType.swapApproval, - params: toTransactionBatchParams( - disable7702, - approval, - quoteResponse.quote.feeData, - ), + params: await toTransactionBatchParams(disable7702, approval, gasFees), }); } + const gasFees = await calculateGasFees( + disable7702, + messagingSystem, + estimateGasFeeFn, + trade, + networkClientId, + hexChainId, + gasIncluded ? txFee : undefined, + ); transactions.push({ type: isBridgeTx ? TransactionType.bridge : TransactionType.swap, - params: toTransactionBatchParams( - disable7702, - trade, - quoteResponse.quote.feeData, - ), + params: await toTransactionBatchParams(disable7702, trade, gasFees), }); const transactionParams: Parameters< TransactionController['addTransactionBatch'] @@ -338,84 +361,5 @@ export const getAddTransactionBatchParams = async ({ transactions, }; - // const isSmartContractAccount = - // selectedAccount.type === EthAccountType.Erc4337; - // if (isSmartContractAccount && this.#addUserOperationFromTransactionFn) { - // const smartAccountTxResult = - // await this.#addUserOperationFromTransactionFn( - // transactionParamsWithMaxGas, - // requestOptions, - // ); - // result = smartAccountTxResult.transactionHash; - // transactionMeta = { - // ...requestOptions, - // chainId: hexChainId, - // txParams: transactionParamsWithMaxGas, - // time: Date.now(), - // id: smartAccountTxResult.id, - // status: TransactionStatus.confirmed, - // }; - // } - return transactionParams; }; - -export const getAddTransactionParams = async ({ - messagingSystem, - estimateGasFeeFn, - transactionType, - trade, - quoteResponse, - hexChainId, - requireApproval = false, -}: { - messagingSystem: BridgeStatusControllerMessenger; - estimateGasFeeFn: typeof TransactionController.prototype.estimateGasFee; - transactionType: TransactionType; - trade: TxData; - quoteResponse: Omit & QuoteMetadata; - hexChainId: Hex; - requireApproval?: boolean; -}) => { - const actionId = generateActionId().toString(); - const networkClientId = messagingSystem.call( - 'NetworkController:findNetworkClientIdByChainId', - hexChainId, - ); - - const requestOptions = { - actionId, - networkClientId, - requireApproval, - transactionType, - origin: 'metamask', - }; - const transactionParams: Parameters< - TransactionController['addTransaction'] - >[0] = { - ...trade, - chainId: hexChainId, - gasLimit: trade.gasLimit?.toString(), - gas: trade.gasLimit?.toString(), - }; - const disable7702 = true; - const transactionParamsWithMaxGas: TransactionParams = { - ...transactionParams, - ...(quoteResponse.quote.feeData[FeeType.TX_FEE] - ? toTransactionBatchParams( - disable7702, - trade, - quoteResponse.quote.feeData, - ) - : await calculateGasFees( - disable7702, - messagingSystem, - estimateGasFeeFn, - transactionParams, - networkClientId, - hexChainId, - )), - }; - - return { transactionParamsWithMaxGas, requestOptions }; -}; From 28153453611812627035cefa915861585b4ab821 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 16:07:39 -0700 Subject: [PATCH 14/23] chore: remove addUserOperationFromTransaction --- .../bridge-status-controller/package.json | 1 - .../bridge-status-controller.test.ts.snap | 446 ------------------ .../src/bridge-status-controller.test.ts | 98 +--- .../src/bridge-status-controller.ts | 53 +-- .../tsconfig.build.json | 3 +- .../bridge-status-controller/tsconfig.json | 3 +- 6 files changed, 11 insertions(+), 593 deletions(-) diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 325add35b76..003f8656013 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -52,7 +52,6 @@ "@metamask/keyring-api": "^18.0.0", "@metamask/polling-controller": "^14.0.0", "@metamask/superstruct": "^3.1.0", - "@metamask/user-operation-controller": "^37.0.0", "@metamask/utils": "^11.2.0", "bignumber.js": "^9.1.2", "uuid": "^8.3.2" 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 13c197ba61a..ca0e9f74530 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 @@ -564,229 +564,6 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM bridge should handle smart accounts (4337) 1`] = ` -Object { - "chainId": "0xa4b1", - "hash": "0xevmTxHash", - "id": "test-tx-id", - "status": "unapproved", - "time": 1234567890, - "txParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gasLimit": "0x5208", - "to": "0xbridgeContract", - "value": "0x0", - }, - "type": "bridge", -} -`; - -exports[`BridgeStatusController submitTx: EVM bridge should handle smart accounts (4337) 2`] = ` -Object { - "account": "0xaccount1", - "approvalTxId": undefined, - "estimatedProcessingTimeInSeconds": 15, - "hasApprovalTx": false, - "initialDestAssetBalance": undefined, - "isStxEnabled": false, - "pricingData": Object { - "amountSent": "1.234", - "amountSentInUsd": undefined, - "quotedGasInUsd": undefined, - "quotedReturnInUsd": undefined, - }, - "quote": Object { - "bridgeId": "lifi", - "bridges": Array [ - "across", - ], - "destAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:10/slip44:60", - "chainId": 10, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.63", - "symbol": "ETH", - }, - "destChainId": 10, - "destTokenAmount": "990654755978612", - "feeData": Object { - "metabridge": Object { - "amount": "8750000000000", - "asset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:42161/slip44:60", - "chainId": 42161, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.7", - "symbol": "ETH", - }, - }, - }, - "requestId": "197c402f-cb96-4096-9f8c-54aed84ca776", - "srcAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:42161/slip44:60", - "chainId": 42161, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.7", - "symbol": "ETH", - }, - "srcChainId": 42161, - "srcTokenAmount": "991250000000000", - "steps": Array [ - Object { - "action": "bridge", - "destAmount": "990654755978612", - "destAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:10/slip44:60", - "chainId": 10, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.63", - "symbol": "ETH", - }, - "destChainId": 10, - "protocol": Object { - "displayName": "Across", - "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png", - "name": "across", - }, - "srcAmount": "991250000000000", - "srcAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:42161/slip44:60", - "chainId": 42161, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.7", - "symbol": "ETH", - }, - "srcChainId": 42161, - }, - ], - }, - "slippagePercentage": 0, - "startTime": 1234567890, - "status": Object { - "srcChain": Object { - "chainId": 42161, - "txHash": "0xevmTxHash", - }, - "status": "PENDING", - }, - "targetContractAddress": undefined, - "txMetaId": "test-tx-id", -} -`; - -exports[`BridgeStatusController submitTx: EVM bridge should handle smart accounts (4337) 3`] = ` -Array [ - Array [ - Object { - "chainId": "0xa4b1", - "networkClientId": "arbitrum", - "transactionParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", - "to": "0xbridgeContract", - "value": "0x0", - }, - }, - ], -] -`; - -exports[`BridgeStatusController submitTx: EVM bridge should handle smart accounts (4337) 4`] = ` -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", - ], - Array [ - "NetworkController:findNetworkClientIdByChainId", - "0xa4b1", - ], - Array [ - "GasFeeController:getState", - ], - Array [ - "TransactionController:getState", - ], - Array [ - "AccountsController:getSelectedMultichainAccount", - ], -] -`; - -exports[`BridgeStatusController submitTx: EVM bridge should handle smart accounts (4337) 5`] = ` -Array [ - Array [ - Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xbridgeContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", - "networkClientId": "arbitrum", - "origin": "metamask", - "requireApproval": false, - "type": "bridge", - }, - ], -] -`; - exports[`BridgeStatusController submitTx: EVM bridge should handle smart transactions 1`] = ` Object { "approvalTxId": undefined, @@ -1927,229 +1704,6 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM swap should handle smart accounts (4337) 1`] = ` -Object { - "chainId": "0xa4b1", - "hash": "0xevmTxHash", - "id": "test-tx-id", - "status": "unapproved", - "time": 1234567890, - "txParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gasLimit": "0x5208", - "to": "0xbridgeContract", - "value": "0x0", - }, - "type": "swap", -} -`; - -exports[`BridgeStatusController submitTx: EVM swap should handle smart accounts (4337) 2`] = ` -Object { - "account": "", - "approvalTxId": undefined, - "estimatedProcessingTimeInSeconds": 0, - "hasApprovalTx": false, - "initialDestAssetBalance": undefined, - "isStxEnabled": false, - "pricingData": Object { - "amountSent": "1.234", - "amountSentInUsd": undefined, - "quotedGasInUsd": undefined, - "quotedReturnInUsd": undefined, - }, - "quote": Object { - "bridgeId": "lifi", - "bridges": Array [ - "across", - ], - "destAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:10/slip44:60", - "chainId": 10, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.63", - "symbol": "ETH", - }, - "destChainId": 42161, - "destTokenAmount": "990654755978612", - "feeData": Object { - "metabridge": Object { - "amount": "8750000000000", - "asset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:42161/slip44:60", - "chainId": 42161, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.7", - "symbol": "ETH", - }, - }, - }, - "requestId": "197c402f-cb96-4096-9f8c-54aed84ca776", - "srcAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:42161/slip44:60", - "chainId": 42161, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.7", - "symbol": "ETH", - }, - "srcChainId": 42161, - "srcTokenAmount": "991250000000000", - "steps": Array [ - Object { - "action": "bridge", - "destAmount": "990654755978612", - "destAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:10/slip44:60", - "chainId": 10, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.63", - "symbol": "ETH", - }, - "destChainId": 10, - "protocol": Object { - "displayName": "Across", - "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png", - "name": "across", - }, - "srcAmount": "991250000000000", - "srcAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:42161/slip44:60", - "chainId": 42161, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.7", - "symbol": "ETH", - }, - "srcChainId": 42161, - }, - ], - }, - "slippagePercentage": 0, - "startTime": 1234567890, - "status": Object { - "srcChain": Object { - "chainId": 42161, - "txHash": "0xevmTxHash", - }, - "status": "PENDING", - }, - "targetContractAddress": undefined, - "txMetaId": "test-tx-id", -} -`; - -exports[`BridgeStatusController submitTx: EVM swap should handle smart accounts (4337) 3`] = ` -Array [ - Array [ - Object { - "chainId": "0xa4b1", - "networkClientId": "arbitrum", - "transactionParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", - "to": "0xbridgeContract", - "value": "0x0", - }, - }, - ], -] -`; - -exports[`BridgeStatusController submitTx: EVM swap should handle smart accounts (4337) 4`] = ` -Array [ - Array [ - "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", - ], - Array [ - "NetworkController:findNetworkClientIdByChainId", - "0xa4b1", - ], - Array [ - "GasFeeController:getState", - ], - Array [ - "TransactionController:getState", - ], - Array [ - "AccountsController:getSelectedMultichainAccount", - ], -] -`; - -exports[`BridgeStatusController submitTx: EVM swap should handle smart accounts (4337) 5`] = ` -Array [ - Array [ - Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xbridgeContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", - "networkClientId": "arbitrum", - "origin": "metamask", - "requireApproval": false, - "type": "swap", - }, - ], -] -`; - exports[`BridgeStatusController submitTx: EVM swap should handle smart transactions 1`] = ` Object { "approvalTxId": undefined, 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 246a5cc771d..5c7e181e86f 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -555,7 +555,6 @@ const executePollingWithPendingStatus = async () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), config: {}, }); const startPollingSpy = jest.spyOn(bridgeStatusController, 'startPolling'); @@ -592,7 +591,6 @@ const mockSelectedAccount = { const addTransactionFn = jest.fn(); const estimateGasFeeFn = jest.fn(); -const addUserOperationFromTransactionFn = jest.fn(); const getController = (call: jest.Mock, traceFn?: jest.Mock) => { const controller = new BridgeStatusController({ @@ -607,7 +605,6 @@ const getController = (call: jest.Mock, traceFn?: jest.Mock) => { fetchFn: mockFetchFn, addTransactionFn, estimateGasFeeFn, - addUserOperationFromTransactionFn, traceFn, }); @@ -633,7 +630,6 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), }); expect(bridgeStatusController.state).toStrictEqual(EMPTY_INIT_STATE); expect(mockMessengerSubscribe.mock.calls).toMatchSnapshot(); @@ -646,7 +642,6 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), state: { txHistory: MockTxHistory.getPending(), }, @@ -705,7 +700,6 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), }); // Execution @@ -743,7 +737,6 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest.spyOn( bridgeStatusUtils, @@ -824,7 +817,6 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), }); // Start polling with args that have no srcTxHash @@ -863,7 +855,6 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest @@ -904,7 +895,6 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), }); // Execution @@ -967,7 +957,6 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), }); // Start polling with no srcTxHash @@ -1055,7 +1044,6 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') @@ -1142,7 +1130,6 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') @@ -1243,7 +1230,6 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') @@ -1921,7 +1907,6 @@ describe('BridgeStatusController', () => { expect(controller.state.txHistory[result.id]).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); }); it('should successfully submit an EVM bridge transaction with no approval', async () => { @@ -1957,7 +1942,6 @@ describe('BridgeStatusController', () => { expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); }); it('should handle smart transactions', async () => { @@ -1975,45 +1959,6 @@ describe('BridgeStatusController', () => { expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); - }); - - it('should handle smart accounts (4337)', async () => { - mockMessengerCall.mockReturnValueOnce({ - ...mockSelectedAccount, - type: EthAccountType.Erc4337, - }); - mockMessengerCall.mockReturnValueOnce('arbitrum'); - estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); - addUserOperationFromTransactionFn.mockResolvedValueOnce({ - id: 'user-op-id', - transactionHash: Promise.resolve('0xevmTxHash'), - hash: Promise.resolve('0xevmTxHash'), - }); - mockMessengerCall.mockReturnValueOnce({ - transactions: [mockEvmTxMeta], - }); - mockMessengerCall.mockReturnValueOnce({ - ...mockSelectedAccount, - type: EthAccountType.Erc4337, - }); - - const { controller, startPollingForBridgeTxStatusSpy } = - getController(mockMessengerCall); - const { approval, ...quoteWithoutApproval } = mockEvmQuoteResponse; - const result = await controller.submitTx(quoteWithoutApproval, false); - controller.stopAllPolling(); - - expect(result).toMatchSnapshot(); - expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); - expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); - expect(addTransactionFn).not.toHaveBeenCalled(); - expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - expect(addUserOperationFromTransactionFn.mock.calls).toMatchSnapshot(); }); it('should throw an error if account is not found', async () => { @@ -2032,7 +1977,6 @@ describe('BridgeStatusController', () => { expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); expect(addTransactionFn).not.toHaveBeenCalled(); - expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); }); it('should reset USDT allowance', async () => { @@ -2080,7 +2024,6 @@ describe('BridgeStatusController', () => { expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); }); it('should throw an error if approval tx meta does not exist', async () => { @@ -2111,7 +2054,6 @@ describe('BridgeStatusController', () => { expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); }); it('should delay after submitting linea approval', async () => { @@ -2291,7 +2233,6 @@ describe('BridgeStatusController', () => { expect(controller.state.txHistory[result.id]).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); }); it('should successfully submit an EVM swap transaction with no approval', async () => { @@ -2327,7 +2268,6 @@ describe('BridgeStatusController', () => { expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); }); it('should handle smart transactions', async () => { @@ -2355,42 +2295,6 @@ describe('BridgeStatusController', () => { expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - expect(addUserOperationFromTransactionFn).not.toHaveBeenCalled(); - }); - - it('should handle smart accounts (4337)', async () => { - mockMessengerCall.mockReturnValueOnce({ - ...mockSelectedAccount, - type: EthAccountType.Erc4337, - }); - mockMessengerCall.mockReturnValueOnce('arbitrum'); - estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); - addUserOperationFromTransactionFn.mockResolvedValueOnce({ - id: 'user-op-id', - transactionHash: Promise.resolve('0xevmTxHash'), - hash: Promise.resolve('0xevmTxHash'), - }); - mockMessengerCall.mockReturnValueOnce({ - transactions: [mockEvmTxMeta], - }); - estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); - - const { controller, startPollingForBridgeTxStatusSpy } = - getController(mockMessengerCall); - const { approval, ...quoteWithoutApproval } = mockEvmQuoteResponse; - const result = await controller.submitTx(quoteWithoutApproval, false); - controller.stopAllPolling(); - - expect(result).toMatchSnapshot(); - expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); - expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); - expect(addTransactionFn).not.toHaveBeenCalled(); - expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - expect(addUserOperationFromTransactionFn.mock.calls).toMatchSnapshot(); }); }); @@ -2457,7 +2361,7 @@ describe('BridgeStatusController', () => { fetchFn: jest.fn(), addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), - addUserOperationFromTransactionFn: jest.fn(), + state: { txHistory: { ...MockTxHistory.getPending(), diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index e2c7f73bfe3..b5bd9a3fa84 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -20,7 +20,7 @@ import { } from '@metamask/bridge-controller'; import type { TraceCallback } from '@metamask/controller-utils'; import { toHex } from '@metamask/controller-utils'; -import { EthAccountType, SolScope } from '@metamask/keyring-api'; +import { SolScope } from '@metamask/keyring-api'; import { StaticIntervalPollingController } from '@metamask/polling-controller'; import type { TransactionController, @@ -31,7 +31,6 @@ import { TransactionType, type TransactionMeta, } from '@metamask/transaction-controller'; -import type { UserOperationController } from '@metamask/user-operation-controller'; import { numberToHex, type Hex } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; @@ -111,8 +110,6 @@ export class BridgeStatusController extends StaticIntervalPollingController>['result'] - | Awaited< - ReturnType - >['hash'], + hashPromise?: Awaited< + ReturnType + >['result'], ): Promise => { const transactionHash = await hashPromise; const finalTransactionMeta: TransactionMeta | undefined = @@ -759,39 +751,10 @@ export class BridgeStatusController extends StaticIntervalPollingController>['result'] - | Awaited< - ReturnType - >['hash'] - | undefined; - let transactionMeta: TransactionMeta | undefined; - - const isSmartContractAccount = - selectedAccount.type === EthAccountType.Erc4337; - if (isSmartContractAccount && this.#addUserOperationFromTransactionFn) { - const smartAccountTxResult = - await this.#addUserOperationFromTransactionFn( - transactionParamsWithMaxGas, - requestOptions, - ); - result = smartAccountTxResult.transactionHash; - transactionMeta = { - ...requestOptions, - chainId: hexChainId, - txParams: transactionParamsWithMaxGas, - time: Date.now(), - id: smartAccountTxResult.id, - status: TransactionStatus.confirmed, - }; - } else { - const addTransactionResult = await this.#addTransactionFn( - transactionParamsWithMaxGas, - requestOptions, - ); - result = addTransactionResult.result; - transactionMeta = addTransactionResult.transactionMeta; - } + const { result, transactionMeta } = await this.#addTransactionFn( + transactionParamsWithMaxGas, + requestOptions, + ); if (shouldWaitForHash) { return await this.#waitForHashAndReturnFinalTxMeta(result); diff --git a/packages/bridge-status-controller/tsconfig.build.json b/packages/bridge-status-controller/tsconfig.build.json index 1ec93edefdf..806aaa6b4df 100644 --- a/packages/bridge-status-controller/tsconfig.build.json +++ b/packages/bridge-status-controller/tsconfig.build.json @@ -13,8 +13,7 @@ { "path": "../network-controller/tsconfig.build.json" }, { "path": "../gas-fee-controller/tsconfig.build.json" }, { "path": "../polling-controller/tsconfig.build.json" }, - { "path": "../transaction-controller/tsconfig.build.json" }, - { "path": "../user-operation-controller/tsconfig.build.json" } + { "path": "../transaction-controller/tsconfig.build.json" } ], "include": ["../../types", "./src"] } diff --git a/packages/bridge-status-controller/tsconfig.json b/packages/bridge-status-controller/tsconfig.json index 7935a0447f7..e41150bdaf3 100644 --- a/packages/bridge-status-controller/tsconfig.json +++ b/packages/bridge-status-controller/tsconfig.json @@ -12,8 +12,7 @@ { "path": "../network-controller" }, { "path": "../polling-controller" }, { "path": "../transaction-controller" }, - { "path": "../gas-fee-controller" }, - { "path": "../user-operation-controller" } + { "path": "../gas-fee-controller" } ], "include": ["../../types", "./src"] } From 96b71a9d4dd00da809b61c43ecc76d5c6a6b90eb Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 16:15:31 -0700 Subject: [PATCH 15/23] chore: update changelog --- packages/bridge-controller/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index cc91e42ccc4..73f571ce965 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **BREAKING:** Bump peer dependency `@metamask/snaps-controllers` from `^12.0.0` to `^14.0.0` ([#6035](https://github.com/MetaMask/core/pull/6035)) +- Remove `addUserOperationFromTransaction` tx submission code since it is unsupported ([#6057](https://github.com/MetaMask/core/pull/6057)) + - Remove `addUserOperationFromTransactionFn` from BridgeStatusController constructor + - Remove @metamask/user-operation-controller dependency ## [34.0.0] From f2896086e40161e8a29fc92ccb16aad87a431cd8 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 16:15:45 -0700 Subject: [PATCH 16/23] chore: update yarn.lock --- yarn.lock | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6ea59387efa..3bcdfaa7ad3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2820,7 +2820,6 @@ __metadata: "@metamask/snaps-controllers": "npm:^14.0.1" "@metamask/superstruct": "npm:^3.1.0" "@metamask/transaction-controller": "npm:^58.1.0" - "@metamask/user-operation-controller": "npm:^37.0.0" "@metamask/utils": "npm:^11.2.0" "@types/jest": "npm:^27.4.1" bignumber.js: "npm:^9.1.2" @@ -4621,7 +4620,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/user-operation-controller@npm:^37.0.0, @metamask/user-operation-controller@workspace:packages/user-operation-controller": +"@metamask/user-operation-controller@workspace:packages/user-operation-controller": version: 0.0.0-use.local resolution: "@metamask/user-operation-controller@workspace:packages/user-operation-controller" dependencies: From b9f95ac2442e0e91f5beccfb2ccf13d442aad022 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 16:19:20 -0700 Subject: [PATCH 17/23] chore: changelog --- packages/bridge-controller/CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 73f571ce965..fa650d8ad9a 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -10,9 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **BREAKING:** Bump peer dependency `@metamask/snaps-controllers` from `^12.0.0` to `^14.0.0` ([#6035](https://github.com/MetaMask/core/pull/6035)) -- Remove `addUserOperationFromTransaction` tx submission code since it is unsupported ([#6057](https://github.com/MetaMask/core/pull/6057)) - - Remove `addUserOperationFromTransactionFn` from BridgeStatusController constructor - - Remove @metamask/user-operation-controller dependency +- Remove `addUserOperationFromTransaction` tx submission code and constructor arg since it is unsupported ([#6057](https://github.com/MetaMask/core/pull/6057)) +- Remove @metamask/user-operation-controller dependency ([#6057](https://github.com/MetaMask/core/pull/6057)) ## [34.0.0] From af58b54900bce59ebdfbbc2130f0a9fa13653ac5 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 16:24:12 -0700 Subject: [PATCH 18/23] fix: lint --- .../src/bridge-status-controller.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index 5c7e181e86f..fb8ca0a234d 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -16,7 +16,6 @@ import { } from '@metamask/bridge-controller'; import { ChainId } from '@metamask/bridge-controller'; import { ActionTypes, FeeType } from '@metamask/bridge-controller'; -import { EthAccountType } from '@metamask/keyring-api'; import { TransactionType, TransactionStatus, From dd44c66ce4fdd1b35e73ae60752b57e0e13d56fb Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 16:38:43 -0700 Subject: [PATCH 19/23] fix: unit tests --- .../src/bridge-status-controller.test.ts | 74 +++++++++++-------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index f3f58673f70..ce1f46e5873 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -551,7 +551,6 @@ const executePollingWithPendingStatus = async () => { messenger: getMessengerMock(), clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), config: {}, @@ -563,7 +562,7 @@ const executePollingWithPendingStatus = async () => { // Execution bridgeStatusController.startPollingForBridgeTxStatus( - getMockStartPollingForBridgeTxStatusArgs().bridgeTxMeta.id, + getMockStartPollingForBridgeTxStatusArgs(), ); fetchBridgeTxStatusSpy.mockImplementationOnce(async () => { return MockStatusResponse.getPending(); @@ -606,7 +605,6 @@ const getController = (call: jest.Mock, traceFn?: jest.Mock) => { } as never, clientId: BridgeClientId.EXTENSION, fetchFn: mockFetchFn, - addTransactionFn, estimateGasFeeFn, addTransactionBatchFn, traceFn, @@ -632,7 +630,6 @@ describe('BridgeStatusController', () => { messenger: getMessengerMock(), clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), }); @@ -645,7 +642,7 @@ describe('BridgeStatusController', () => { messenger: getMessengerMock(), clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), + estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), state: { @@ -655,7 +652,7 @@ describe('BridgeStatusController', () => { // Execution bridgeStatusController.startPollingForBridgeTxStatus( - getMockStartPollingForBridgeTxStatusArgs().bridgeTxMeta.id, + getMockStartPollingForBridgeTxStatusArgs(), ); // Assertion @@ -682,7 +679,6 @@ describe('BridgeStatusController', () => { }, clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), }); @@ -694,7 +690,6 @@ describe('BridgeStatusController', () => { }); }); - /* describe('startPollingForBridgeTxStatus', () => { beforeEach(() => { jest.clearAllMocks(); @@ -706,7 +701,7 @@ describe('BridgeStatusController', () => { messenger: getMessengerMock(), clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), + estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), state: { @@ -747,7 +742,7 @@ describe('BridgeStatusController', () => { messenger: messengerMock, clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), + estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), }); @@ -828,7 +823,7 @@ describe('BridgeStatusController', () => { messenger: messengerMock, clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), + estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), }); @@ -867,8 +862,8 @@ describe('BridgeStatusController', () => { messenger: messengerMock, clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), + addTransactionBatchFn: jest.fn(), }); const fetchBridgeTxStatusSpy = jest @@ -907,7 +902,7 @@ describe('BridgeStatusController', () => { messenger: messengerMock, clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), + estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), }); @@ -970,7 +965,6 @@ describe('BridgeStatusController', () => { messenger: messengerMock, clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), }); @@ -1000,7 +994,6 @@ describe('BridgeStatusController', () => { jest.restoreAllMocks(); }); }); - */ describe('resetState', () => { it('resets the state', async () => { @@ -1060,7 +1053,7 @@ describe('BridgeStatusController', () => { messenger: messengerMock, clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), + estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), }); @@ -1147,7 +1140,7 @@ describe('BridgeStatusController', () => { messenger: messengerMock, clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), + estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), }); @@ -1248,7 +1241,7 @@ describe('BridgeStatusController', () => { messenger: messengerMock, clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), + estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), }); @@ -1484,7 +1477,9 @@ describe('BridgeStatusController', () => { expect( startPollingForBridgeTxStatusSpy.mock.lastCall[0], ).toMatchSnapshot(); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect( + controller.state.txHistory[result.batchId as never], + ).toMatchSnapshot(); }); it('should throw error when snap ID is missing', async () => { @@ -1722,7 +1717,9 @@ describe('BridgeStatusController', () => { expect(mockMessengerCall.mock.calls).toMatchSnapshot(); expect(result).toMatchSnapshot(); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect( + controller.state.txHistory[result.batchId as never], + ).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); }); @@ -1787,7 +1784,7 @@ describe('BridgeStatusController', () => { expect(startPollingForBridgeTxStatusSpy).not.toHaveBeenCalled(); }); }); - /* + describe('submitTx: EVM bridge', () => { const mockEvmQuoteResponse = { ...getMockQuote(), @@ -1926,7 +1923,9 @@ describe('BridgeStatusController', () => { expect(result).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect( + controller.state.txHistory[result.batchId as never], + ).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); }); @@ -1960,7 +1959,9 @@ describe('BridgeStatusController', () => { expect(result).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect( + controller.state.txHistory[result.batchId as never], + ).toMatchSnapshot(); expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); @@ -1977,7 +1978,9 @@ describe('BridgeStatusController', () => { expect(result).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect( + controller.state.txHistory[result.batchId as never], + ).toMatchSnapshot(); expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); @@ -2021,7 +2024,9 @@ describe('BridgeStatusController', () => { expect(result).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect( + controller.state.txHistory[result.batchId as never], + ).toMatchSnapshot(); expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); @@ -2110,7 +2115,9 @@ describe('BridgeStatusController', () => { expect(handleLineaDelaySpy).toHaveBeenCalledTimes(1); expect(result).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect( + controller.state.txHistory[result.batchId as never], + ).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); expect(mockTraceFn.mock.calls).toMatchSnapshot(); }); @@ -2252,7 +2259,9 @@ describe('BridgeStatusController', () => { expect(result).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect( + controller.state.txHistory[result.batchId as never], + ).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); }); @@ -2286,7 +2295,9 @@ describe('BridgeStatusController', () => { expect(result).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect( + controller.state.txHistory[result.batchId as never], + ).toMatchSnapshot(); expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); @@ -2313,13 +2324,14 @@ describe('BridgeStatusController', () => { expect(result).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(controller.state.txHistory[result.id]).toMatchSnapshot(); + expect( + controller.state.txHistory[result.batchId as never], + ).toMatchSnapshot(); expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); expect(addTransactionFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); }); }); - */ describe('subscription handlers', () => { let mockBridgeStatusMessenger: jest.Mocked; @@ -2382,7 +2394,7 @@ describe('BridgeStatusController', () => { messenger: mockBridgeStatusMessenger, clientId: BridgeClientId.EXTENSION, fetchFn: jest.fn(), - addTransactionFn: jest.fn(), + estimateGasFeeFn: jest.fn(), addTransactionBatchFn: jest.fn(), state: { From f6c0173e774a1c3d480673d91b64f607c71fde01 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 16:39:57 -0700 Subject: [PATCH 20/23] fix: unit tests --- .../src/bridge-status-controller.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index ce1f46e5873..34a3eb221b6 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -1010,7 +1010,6 @@ describe('BridgeStatusController', () => { }); }); - /* describe('wipeBridgeStatus', () => { it('wipes the bridge status for the given address', async () => { // Setup @@ -1313,7 +1312,6 @@ describe('BridgeStatusController', () => { expect(txHistoryItems[0].quote.destChainId).toBe(123); }); }); - */ describe('submitTx: Solana bridge', () => { const mockQuoteResponse: QuoteResponse & QuoteMetadata = { From 2279d3bc9e7d307e12a8c8cb492517d83ad80acd Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 16:43:43 -0700 Subject: [PATCH 21/23] fix: solana tests --- .../__snapshots__/bridge-status-controller.test.ts.snap | 2 ++ .../src/bridge-status-controller.test.ts | 8 ++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap b/packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap index ca0e9f74530..2779a3fb5fb 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 @@ -2555,6 +2555,7 @@ exports[`BridgeStatusController submitTx: Solana bridge should successfully subm Object { "account": "0x123...", "approvalTxId": undefined, + "batchId": undefined, "estimatedProcessingTimeInSeconds": 300, "hasApprovalTx": false, "initialDestAssetBalance": undefined, @@ -2886,6 +2887,7 @@ exports[`BridgeStatusController submitTx: Solana swap should successfully submit Object { "account": "0x123...", "approvalTxId": undefined, + "batchId": undefined, "estimatedProcessingTimeInSeconds": 300, "hasApprovalTx": false, "initialDestAssetBalance": undefined, 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 34a3eb221b6..d2c33c6eab9 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -1475,9 +1475,7 @@ describe('BridgeStatusController', () => { expect( startPollingForBridgeTxStatusSpy.mock.lastCall[0], ).toMatchSnapshot(); - expect( - controller.state.txHistory[result.batchId as never], - ).toMatchSnapshot(); + expect(controller.state.txHistory[result.id as never]).toMatchSnapshot(); }); it('should throw error when snap ID is missing', async () => { @@ -1715,9 +1713,7 @@ describe('BridgeStatusController', () => { expect(mockMessengerCall.mock.calls).toMatchSnapshot(); expect(result).toMatchSnapshot(); - expect( - controller.state.txHistory[result.batchId as never], - ).toMatchSnapshot(); + expect(controller.state.txHistory[result.id as never]).toMatchSnapshot(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); }); From cf502df5916547156e74e1822c79b60f543cede7 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 16:59:21 -0700 Subject: [PATCH 22/23] fix: addTransactionBatch snapshots --- .../bridge-status-controller.test.ts.snap | 926 ++++-------------- .../src/bridge-status-controller.test.ts | 185 +--- 2 files changed, 241 insertions(+), 870 deletions(-) diff --git a/packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap b/packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap index 2779a3fb5fb..ba3558a8c3d 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 @@ -5,6 +5,7 @@ Object { "bridgeTxMetaId1": Object { "account": "0xaccount1", "approvalTxId": undefined, + "batchId": undefined, "estimatedProcessingTimeInSeconds": 15, "hasApprovalTx": false, "initialDestAssetBalance": undefined, @@ -185,6 +186,7 @@ Object { "bridgeTxMetaId1": Object { "account": "0xaccount1", "approvalTxId": undefined, + "batchId": undefined, "estimatedProcessingTimeInSeconds": 15, "hasApprovalTx": false, "initialDestAssetBalance": undefined, @@ -347,257 +349,17 @@ Array [ ] `; -exports[`BridgeStatusController submitTx: EVM bridge should delay after submitting linea approval 1`] = ` -Object { - "chainId": "0xa4b1", - "hash": "0xevmTxHash", - "id": "test-tx-id", - "status": "unapproved", - "time": 1234567890, - "txParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gasLimit": "0x5208", - "to": "0xbridgeContract", - "value": "0x0", - }, - "type": "bridge", -} -`; - -exports[`BridgeStatusController submitTx: EVM bridge should delay after submitting linea approval 2`] = ` -Object { - "account": "0xaccount1", - "approvalTxId": "test-approval-tx-id", - "estimatedProcessingTimeInSeconds": 15, - "hasApprovalTx": true, - "initialDestAssetBalance": undefined, - "isStxEnabled": false, - "pricingData": Object { - "amountSent": "1.234", - "amountSentInUsd": undefined, - "quotedGasInUsd": undefined, - "quotedReturnInUsd": undefined, - }, - "quote": Object { - "bridgeId": "lifi", - "bridges": Array [ - "across", - ], - "destAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:10/slip44:60", - "chainId": 10, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.63", - "symbol": "ETH", - }, - "destChainId": 10, - "destTokenAmount": "990654755978612", - "feeData": Object { - "metabridge": Object { - "amount": "8750000000000", - "asset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:42161/slip44:60", - "chainId": 42161, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.7", - "symbol": "ETH", - }, - }, - }, - "requestId": "197c402f-cb96-4096-9f8c-54aed84ca776", - "srcAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:42161/slip44:60", - "chainId": 42161, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.7", - "symbol": "ETH", - }, - "srcChainId": 59144, - "srcTokenAmount": "991250000000000", - "steps": Array [ - Object { - "action": "bridge", - "destAmount": "990654755978612", - "destAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:10/slip44:60", - "chainId": 10, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.63", - "symbol": "ETH", - }, - "destChainId": 10, - "protocol": Object { - "displayName": "Across", - "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png", - "name": "across", - }, - "srcAmount": "991250000000000", - "srcAsset": Object { - "address": "0x0000000000000000000000000000000000000000", - "assetId": "eip155:42161/slip44:60", - "chainId": 42161, - "coinKey": "ETH", - "decimals": 18, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "name": "ETH", - "priceUSD": "2478.7", - "symbol": "ETH", - }, - "srcChainId": 42161, - }, - ], - }, - "slippagePercentage": 0, - "startTime": 1234567890, - "status": Object { - "srcChain": Object { - "chainId": 59144, - "txHash": "0xevmTxHash", - }, - "status": "PENDING", - }, - "targetContractAddress": undefined, - "txMetaId": "test-tx-id", -} -`; - -exports[`BridgeStatusController submitTx: EVM bridge should delay after submitting linea approval 3`] = ` -Array [ - Array [ - "BridgeController:stopPollingForQuotes", - ], - Array [ - "BridgeController:trackUnifiedSwapBridgeEvent", - "Unified SwapBridge Submitted", - Object { - "gas_included": false, - "price_impact": 0, - "provider": "lifi_across", - "quoted_time_minutes": 0.25, - "stx_enabled": false, - "token_symbol_destination": "ETH", - "token_symbol_source": "ETH", - "usd_amount_source": 0, - "usd_quoted_gas": 0, - "usd_quoted_return": 0, - }, - ], - Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", - ], - Array [ - "NetworkController:findNetworkClientIdByChainId", - "0xa4b1", - ], - Array [ - "GasFeeController:getState", - ], - Array [ - "TransactionController:getState", - ], - Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", - ], - Array [ - "NetworkController:findNetworkClientIdByChainId", - "0xa4b1", - ], - Array [ - "GasFeeController:getState", - ], - Array [ - "TransactionController:getState", - ], - Array [ - "AccountsController:getSelectedMultichainAccount", - ], -] -`; - -exports[`BridgeStatusController submitTx: EVM bridge should delay after submitting linea approval 4`] = ` -Array [ - Array [ - Object { - "data": Object { - "srcChainId": "eip155:59144", - "stxEnabled": false, - }, - "name": "Bridge Transaction Approval Completed", - }, - [Function], - ], - Array [ - Object { - "data": Object { - "srcChainId": "eip155:59144", - "stxEnabled": false, - }, - "name": "Bridge Transaction Completed", - }, - [Function], - ], -] -`; - exports[`BridgeStatusController submitTx: EVM bridge should handle smart transactions 1`] = ` Object { - "approvalTxId": undefined, - "chainId": "0xa4b1", - "destinationChainId": "0xa", - "destinationTokenAddress": "0x0000000000000000000000000000000000000000", - "destinationTokenAmount": "990654755978612", - "destinationTokenDecimals": 18, - "destinationTokenSymbol": "ETH", - "hash": "0xevmTxHash", - "id": "test-tx-id", - "sourceTokenAddress": "0x0000000000000000000000000000000000000000", - "sourceTokenAmount": "991250000000000", - "sourceTokenDecimals": 18, - "sourceTokenSymbol": "ETH", - "status": "unapproved", - "swapTokenValue": "1.234", - "time": 1234567890, - "txParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gasLimit": "0x5208", - "to": "0xbridgeContract", - "value": "0x0", - }, - "type": "bridge", + "batchId": "0xevmTxHash", } `; exports[`BridgeStatusController submitTx: EVM bridge should handle smart transactions 2`] = ` Object { - "account": "", + "account": "0xaccount1", "approvalTxId": undefined, + "batchId": "0xevmTxHash", "estimatedProcessingTimeInSeconds": 15, "hasApprovalTx": false, "initialDestAssetBalance": undefined, @@ -703,12 +465,12 @@ Object { "status": Object { "srcChain": Object { "chainId": 42161, - "txHash": "0xevmTxHash", + "txHash": undefined, }, "status": "PENDING", }, "targetContractAddress": undefined, - "txMetaId": "test-tx-id", + "txMetaId": undefined, } `; @@ -719,11 +481,9 @@ Array [ "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": Object { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "21000", - "gasLimit": "21000", "to": "0xbridgeContract", "value": "0x0", }, @@ -736,22 +496,25 @@ exports[`BridgeStatusController submitTx: EVM bridge should handle smart transac Array [ Array [ Object { - "chainId": "0xa4b1", - "data": "0xdata", + "disable7702": true, "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xbridgeContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", "networkClientId": "arbitrum", "origin": "metamask", "requireApproval": false, - "type": "bridge", + "transactions": Array [ + Object { + "params": Object { + "data": "0xdata", + "from": "0xaccount1", + "gas": "0x5208", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "to": "0xbridgeContract", + "value": "0x0", + }, + "type": "bridge", + }, + ], }, ], ] @@ -797,27 +560,15 @@ Array [ exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance 1`] = ` Object { - "chainId": "0xa4b1", - "hash": "0xevmTxHash", - "id": "test-tx-id", - "status": "unapproved", - "time": 1234567890, - "txParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gasLimit": "0x5208", - "to": "0xbridgeContract", - "value": "0x0", - }, - "type": "bridge", + "batchId": "0xapprovalTxHash", } `; exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance 2`] = ` Object { - "account": "0xaccount1", - "approvalTxId": "test-approval-tx-id", + "account": "", + "approvalTxId": undefined, + "batchId": "0xapprovalTxHash", "estimatedProcessingTimeInSeconds": 15, "hasApprovalTx": true, "initialDestAssetBalance": undefined, @@ -923,12 +674,12 @@ Object { "status": Object { "srcChain": Object { "chainId": 42161, - "txHash": "0xevmTxHash", + "txHash": undefined, }, "status": "PENDING", }, "targetContractAddress": undefined, - "txMetaId": "test-tx-id", + "txMetaId": undefined, } `; @@ -937,28 +688,15 @@ Array [ Array [ Object { "chainId": "0xa4b1", - "networkClientId": "arbitrum-client-id", - "transactionParams": Object { - "chainId": "0xa4b1", - "data": "0x095ea7b30000000000000000000000000439e60f02a8900a951603950d8d4527f400c3f10000000000000000000000000000000000000000000000000000000000000000", - "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", - "to": "0xtokenContract", - "value": "0x0", + "networkClientId": Object { + "gasFeeEstimates": Object { + "estimatedBaseFee": "0x1234", + }, }, - }, - ], - Array [ - Object { - "chainId": "0xa4b1", - "networkClientId": "arbitrum-client-id", "transactionParams": Object { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "21000", - "gasLimit": "21000", "to": "0xtokenContract", "value": "0x0", }, @@ -967,13 +705,15 @@ Array [ Array [ Object { "chainId": "0xa4b1", - "networkClientId": "arbitrum", + "networkClientId": Object { + "gasFeeEstimates": Object { + "estimatedBaseFee": "0x1234", + }, + }, "transactionParams": Object { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "21000", - "gasLimit": "21000", "to": "0xbridgeContract", "value": "0x0", }, @@ -986,62 +726,41 @@ exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance Array [ Array [ Object { - "chainId": "0xa4b1", - "data": "0x095ea7b30000000000000000000000000439e60f02a8900a951603950d8d4527f400c3f10000000000000000000000000000000000000000000000000000000000000000", + "disable7702": true, "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xtokenContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", - "networkClientId": "arbitrum-client-id", - "origin": "metamask", - "requireApproval": false, - "type": "bridgeApproval", - }, - ], - Array [ - Object { - "chainId": "0xa4b1", - "data": "0xapprovalData", - "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xtokenContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", - "networkClientId": "arbitrum-client-id", - "origin": "metamask", - "requireApproval": false, - "type": "bridgeApproval", - }, - ], - Array [ - Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xbridgeContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", - "networkClientId": "arbitrum", + "networkClientId": Object { + "gasFeeEstimates": Object { + "estimatedBaseFee": "0x1234", + }, + }, "origin": "metamask", "requireApproval": false, - "type": "bridge", + "transactions": Array [ + Object { + "params": Object { + "data": "0xapprovalData", + "from": "0xaccount1", + "gas": "0x5208", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "to": "0xtokenContract", + "value": "0x0", + }, + "type": "bridgeApproval", + }, + Object { + "params": Object { + "data": "0xdata", + "from": "0xaccount1", + "gas": "0x5208", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "to": "0xbridgeContract", + "value": "0x0", + }, + "type": "bridge", + }, + ], }, ], ] @@ -1084,37 +803,9 @@ Array [ Array [ "GasFeeController:getState", ], - Array [ - "TransactionController:getState", - ], - Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", - ], - Array [ - "NetworkController:findNetworkClientIdByChainId", - "0xa4b1", - ], - Array [ - "GasFeeController:getState", - ], - Array [ - "TransactionController:getState", - ], - Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", - ], - Array [ - "NetworkController:findNetworkClientIdByChainId", - "0xa4b1", - ], Array [ "GasFeeController:getState", ], - Array [ - "TransactionController:getState", - ], Array [ "AccountsController:getSelectedMultichainAccount", ], @@ -1123,27 +814,15 @@ Array [ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with approval 1`] = ` Object { - "chainId": "0xa4b1", - "hash": "0xevmTxHash", - "id": "test-tx-id", - "status": "unapproved", - "time": 1234567890, - "txParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gasLimit": "0x5208", - "to": "0xbridgeContract", - "value": "0x0", - }, - "type": "bridge", + "batchId": "0xapprovalTxHash", } `; exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with approval 2`] = ` Object { "account": "0xaccount1", - "approvalTxId": "test-approval-tx-id", + "approvalTxId": undefined, + "batchId": "0xapprovalTxHash", "estimatedProcessingTimeInSeconds": 15, "hasApprovalTx": true, "initialDestAssetBalance": undefined, @@ -1249,12 +928,12 @@ Object { "status": Object { "srcChain": Object { "chainId": 42161, - "txHash": "0xevmTxHash", + "txHash": undefined, }, "status": "PENDING", }, "targetContractAddress": undefined, - "txMetaId": "test-tx-id", + "txMetaId": undefined, } `; @@ -1262,42 +941,37 @@ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit Array [ Array [ Object { - "chainId": "0xa4b1", - "data": "0xapprovalData", + "disable7702": true, "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xtokenContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", "networkClientId": "arbitrum-client-id", "origin": "metamask", "requireApproval": false, - "type": "bridgeApproval", - }, - ], - Array [ - Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xbridgeContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", - "networkClientId": "arbitrum", - "origin": "metamask", - "requireApproval": false, - "type": "bridge", + "transactions": Array [ + Object { + "params": Object { + "data": "0xapprovalData", + "from": "0xaccount1", + "gas": "0x5208", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "to": "0xtokenContract", + "value": "0x0", + }, + "type": "bridgeApproval", + }, + Object { + "params": Object { + "data": "0xdata", + "from": "0xaccount1", + "gas": "0x5208", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "to": "0xbridgeContract", + "value": "0x0", + }, + "type": "bridge", + }, + ], }, ], ] @@ -1335,23 +1009,9 @@ Array [ Array [ "GasFeeController:getState", ], - Array [ - "TransactionController:getState", - ], - Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", - ], - Array [ - "NetworkController:findNetworkClientIdByChainId", - "0xa4b1", - ], Array [ "GasFeeController:getState", ], - Array [ - "TransactionController:getState", - ], Array [ "AccountsController:getSelectedMultichainAccount", ], @@ -1360,20 +1020,7 @@ Array [ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit an EVM bridge transaction with no approval 1`] = ` Object { - "chainId": "0xa4b1", - "hash": "0xevmTxHash", - "id": "test-tx-id", - "status": "unapproved", - "time": 1234567890, - "txParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gasLimit": "0x5208", - "to": "0xbridgeContract", - "value": "0x0", - }, - "type": "bridge", + "batchId": "0xevmTxHash", } `; @@ -1381,6 +1028,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit Object { "account": "0xaccount1", "approvalTxId": undefined, + "batchId": "0xevmTxHash", "estimatedProcessingTimeInSeconds": 15, "hasApprovalTx": false, "initialDestAssetBalance": undefined, @@ -1486,12 +1134,12 @@ Object { "status": Object { "srcChain": Object { "chainId": 42161, - "txHash": "0xevmTxHash", + "txHash": undefined, }, "status": "PENDING", }, "targetContractAddress": undefined, - "txMetaId": "test-tx-id", + "txMetaId": undefined, } `; @@ -1502,11 +1150,9 @@ Array [ "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": Object { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "21000", - "gasLimit": "21000", "to": "0xbridgeContract", "value": "0x0", }, @@ -1519,22 +1165,25 @@ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit Array [ Array [ Object { - "chainId": "0xa4b1", - "data": "0xdata", + "disable7702": true, "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xbridgeContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", "networkClientId": "arbitrum", "origin": "metamask", "requireApproval": false, - "type": "bridge", + "transactions": Array [ + Object { + "params": Object { + "data": "0xdata", + "from": "0xaccount1", + "gas": "0x5208", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "to": "0xbridgeContract", + "value": "0x0", + }, + "type": "bridge", + }, + ], }, ], ] @@ -1572,165 +1221,15 @@ Array [ Array [ "GasFeeController:getState", ], - Array [ - "TransactionController:getState", - ], Array [ "AccountsController:getSelectedMultichainAccount", ], ] `; -exports[`BridgeStatusController submitTx: EVM bridge should throw an error if approval tx fails 1`] = ` -Array [ - Array [ - Object { - "chainId": "0xa4b1", - "data": "0xapprovalData", - "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xtokenContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", - "networkClientId": "arbitrum-client-id", - "origin": "metamask", - "requireApproval": false, - "type": "bridgeApproval", - }, - ], -] -`; - -exports[`BridgeStatusController submitTx: EVM bridge should throw an error if approval tx fails 2`] = ` -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", - ], - Array [ - "NetworkController:findNetworkClientIdByChainId", - "0xa4b1", - ], - Array [ - "GasFeeController:getState", - ], -] -`; - -exports[`BridgeStatusController submitTx: EVM bridge should throw an error if approval tx meta does not exist 1`] = ` -Array [ - Array [ - Object { - "chainId": "0xa4b1", - "data": "0xapprovalData", - "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xtokenContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", - "networkClientId": "arbitrum-client-id", - "origin": "metamask", - "requireApproval": false, - "type": "bridgeApproval", - }, - ], -] -`; - -exports[`BridgeStatusController submitTx: EVM bridge should throw an error if approval tx meta does not exist 2`] = ` -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", - ], - Array [ - "NetworkController:findNetworkClientIdByChainId", - "0xa4b1", - ], - Array [ - "GasFeeController:getState", - ], - Array [ - "TransactionController:getState", - ], -] -`; - exports[`BridgeStatusController submitTx: EVM swap should handle smart transactions 1`] = ` Object { - "approvalTxId": undefined, - "chainId": "0xa4b1", - "destinationChainId": "0xa4b1", - "destinationTokenAddress": "0x0000000000000000000000000000000000000000", - "destinationTokenAmount": "990654755978612", - "destinationTokenDecimals": 18, - "destinationTokenSymbol": "ETH", - "hash": "0xevmTxHash", - "id": "test-tx-id", - "sourceTokenAddress": "0x0000000000000000000000000000000000000000", - "sourceTokenAmount": "991250000000000", - "sourceTokenDecimals": 18, - "sourceTokenSymbol": "ETH", - "status": "unapproved", - "swapTokenValue": "1.234", - "time": 1234567890, - "txParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gasLimit": "0x5208", - "to": "0xbridgeContract", - "value": "0x0", - }, - "type": "swap", + "batchId": "0xevmTxHash", } `; @@ -1738,6 +1237,7 @@ exports[`BridgeStatusController submitTx: EVM swap should handle smart transacti Object { "account": "", "approvalTxId": undefined, + "batchId": "0xevmTxHash", "estimatedProcessingTimeInSeconds": 0, "hasApprovalTx": false, "initialDestAssetBalance": undefined, @@ -1843,12 +1343,12 @@ Object { "status": Object { "srcChain": Object { "chainId": 42161, - "txHash": "0xevmTxHash", + "txHash": undefined, }, "status": "PENDING", }, "targetContractAddress": undefined, - "txMetaId": "test-tx-id", + "txMetaId": undefined, } `; @@ -1859,11 +1359,9 @@ Array [ "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": Object { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "21000", - "gasLimit": "21000", "to": "0xbridgeContract", "value": "0x0", }, @@ -1876,22 +1374,25 @@ exports[`BridgeStatusController submitTx: EVM swap should handle smart transacti Array [ Array [ Object { - "chainId": "0xa4b1", - "data": "0xdata", + "disable7702": true, "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xbridgeContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", "networkClientId": "arbitrum", "origin": "metamask", "requireApproval": false, - "type": "swap", + "transactions": Array [ + Object { + "params": Object { + "data": "0xdata", + "from": "0xaccount1", + "gas": "0x5208", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "to": "0xbridgeContract", + "value": "0x0", + }, + "type": "swap", + }, + ], }, ], ] @@ -1937,27 +1438,15 @@ Array [ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with approval 1`] = ` Object { - "chainId": "0xa4b1", - "hash": "0xevmTxHash", - "id": "test-tx-id", - "status": "unapproved", - "time": 1234567890, - "txParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gasLimit": "0x5208", - "to": "0xbridgeContract", - "value": "0x0", - }, - "type": "swap", + "batchId": "0xapprovalTxHash", } `; exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with approval 2`] = ` Object { - "account": "0xaccount1", - "approvalTxId": "test-approval-tx-id", + "account": "", + "approvalTxId": undefined, + "batchId": "0xapprovalTxHash", "estimatedProcessingTimeInSeconds": 0, "hasApprovalTx": true, "initialDestAssetBalance": undefined, @@ -2063,12 +1552,12 @@ Object { "status": Object { "srcChain": Object { "chainId": 42161, - "txHash": "0xevmTxHash", + "txHash": undefined, }, "status": "PENDING", }, "targetContractAddress": undefined, - "txMetaId": "test-tx-id", + "txMetaId": undefined, } `; @@ -2076,42 +1565,37 @@ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an Array [ Array [ Object { - "chainId": "0xa4b1", - "data": "0xapprovalData", + "disable7702": true, "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xtokenContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", "networkClientId": "arbitrum-client-id", "origin": "metamask", "requireApproval": false, - "type": "swapApproval", - }, - ], - Array [ - Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xbridgeContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", - "networkClientId": "arbitrum", - "origin": "metamask", - "requireApproval": false, - "type": "swap", + "transactions": Array [ + Object { + "params": Object { + "data": "0xapprovalData", + "from": "0xaccount1", + "gas": "0x5208", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "to": "0xtokenContract", + "value": "0x0", + }, + "type": "swapApproval", + }, + Object { + "params": Object { + "data": "0xdata", + "from": "0xaccount1", + "gas": "0x5208", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "to": "0xbridgeContract", + "value": "0x0", + }, + "type": "swap", + }, + ], }, ], ] @@ -2149,23 +1633,9 @@ Array [ Array [ "GasFeeController:getState", ], - Array [ - "TransactionController:getState", - ], - Array [ - "AccountsController:getAccountByAddress", - "0xaccount1", - ], - Array [ - "NetworkController:findNetworkClientIdByChainId", - "0xa4b1", - ], Array [ "GasFeeController:getState", ], - Array [ - "TransactionController:getState", - ], Array [ "AccountsController:getSelectedMultichainAccount", ], @@ -2174,20 +1644,7 @@ Array [ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an EVM swap transaction with no approval 1`] = ` Object { - "chainId": "0xa4b1", - "hash": "0xevmTxHash", - "id": "test-tx-id", - "status": "unapproved", - "time": 1234567890, - "txParams": Object { - "chainId": "0xa4b1", - "data": "0xdata", - "from": "0xaccount1", - "gasLimit": "0x5208", - "to": "0xbridgeContract", - "value": "0x0", - }, - "type": "swap", + "batchId": "0xevmTxHash", } `; @@ -2195,6 +1652,7 @@ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an Object { "account": "0xaccount1", "approvalTxId": undefined, + "batchId": "0xevmTxHash", "estimatedProcessingTimeInSeconds": 0, "hasApprovalTx": false, "initialDestAssetBalance": undefined, @@ -2300,12 +1758,12 @@ Object { "status": Object { "srcChain": Object { "chainId": 42161, - "txHash": "0xevmTxHash", + "txHash": undefined, }, "status": "PENDING", }, "targetContractAddress": undefined, - "txMetaId": "test-tx-id", + "txMetaId": undefined, } `; @@ -2316,11 +1774,9 @@ Array [ "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": Object { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "21000", - "gasLimit": "21000", "to": "0xbridgeContract", "value": "0x0", }, @@ -2333,22 +1789,25 @@ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an Array [ Array [ Object { - "chainId": "0xa4b1", - "data": "0xdata", + "disable7702": true, "from": "0xaccount1", - "gas": "0x5208", - "gasLimit": "21000", - "maxFeePerGas": undefined, - "maxPriorityFeePerGas": undefined, - "to": "0xbridgeContract", - "value": "0x0", - }, - Object { - "actionId": "1234567890.456", "networkClientId": "arbitrum", "origin": "metamask", "requireApproval": false, - "type": "swap", + "transactions": Array [ + Object { + "params": Object { + "data": "0xdata", + "from": "0xaccount1", + "gas": "0x5208", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "to": "0xbridgeContract", + "value": "0x0", + }, + "type": "swap", + }, + ], }, ], ] @@ -2386,9 +1845,6 @@ Array [ Array [ "GasFeeController:getState", ], - Array [ - "TransactionController:getState", - ], Array [ "AccountsController:getSelectedMultichainAccount", ], 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 d2c33c6eab9..38886d6550a 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -358,12 +358,14 @@ const MockTxHistory = { }), getPending: ({ txMetaId = 'bridgeTxMetaId1', + batchId = undefined, srcTxHash = '0xsrcTxHash1', account = '0xaccount1', srcChainId = 42161, destChainId = 10, } = {}): Record => ({ [txMetaId]: { + batchId, txMetaId, quote: getMockQuote({ srcChainId, destChainId }), startTime: 1729964825189, @@ -456,6 +458,7 @@ const MockTxHistory = { }), getComplete: ({ txMetaId = 'bridgeTxMetaId1', + batchId = undefined, srcTxHash = '0xsrcTxHash1', account = '0xaccount1', srcChainId = 42161, @@ -463,6 +466,7 @@ const MockTxHistory = { } = {}): Record => ({ [txMetaId]: { txMetaId, + batchId, quote: getMockQuote({ srcChainId, destChainId }), startTime: 1729964825189, completionTime: 1736277625746, @@ -590,7 +594,6 @@ const mockSelectedAccount = { }, }; -const addTransactionFn = jest.fn(); const estimateGasFeeFn = jest.fn(); const addTransactionBatchFn = jest.fn(); @@ -1879,12 +1882,33 @@ describe('BridgeStatusController', () => { gasFeeEstimates: { estimatedBaseFee: '0x1234' }, }); estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); - addTransactionFn.mockResolvedValueOnce({ - transactionMeta: mockApprovalTxMeta, - result: Promise.resolve('0xapprovalTxHash'), + mockMessengerCall.mockReturnValueOnce({ + gasFeeEstimates: { estimatedBaseFee: '0x1234' }, + }); + estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); + addTransactionBatchFn.mockResolvedValueOnce({ + batchId: '0xapprovalTxHash', }); + mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); + }; + + const setup2ApprovalMocks = () => { + mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); + mockMessengerCall.mockReturnValueOnce('arbitrum-client-id'); mockMessengerCall.mockReturnValueOnce({ - transactions: [mockApprovalTxMeta], + gasFeeEstimates: { estimatedBaseFee: '0x1234' }, + }); + estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); + mockMessengerCall.mockReturnValueOnce({ + gasFeeEstimates: { estimatedBaseFee: '0x1234' }, + }); + estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); + mockMessengerCall.mockReturnValueOnce({ + gasFeeEstimates: { estimatedBaseFee: '0x1234' }, + }); + estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); + addTransactionBatchFn.mockResolvedValueOnce({ + batchId: '0xapprovalTxHash', }); }; @@ -1895,12 +1919,8 @@ describe('BridgeStatusController', () => { gasFeeEstimates: { estimatedBaseFee: '0x1234' }, }); estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); - addTransactionFn.mockResolvedValueOnce({ - transactionMeta: mockEvmTxMeta, - result: Promise.resolve('0xevmTxHash'), - }); - mockMessengerCall.mockReturnValueOnce({ - transactions: [mockEvmTxMeta], + addTransactionBatchFn.mockResolvedValueOnce({ + batchId: '0xevmTxHash', }); mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); @@ -1908,7 +1928,6 @@ describe('BridgeStatusController', () => { it('should successfully submit an EVM bridge transaction with approval', async () => { setupApprovalMocks(); - setupBridgeMocks(); const { controller, startPollingForBridgeTxStatusSpy } = getController(mockMessengerCall); @@ -1920,7 +1939,7 @@ describe('BridgeStatusController', () => { expect( controller.state.txHistory[result.batchId as never], ).toMatchSnapshot(); - expect(addTransactionFn.mock.calls).toMatchSnapshot(); + expect(addTransactionBatchFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); }); @@ -1957,7 +1976,7 @@ describe('BridgeStatusController', () => { controller.state.txHistory[result.batchId as never], ).toMatchSnapshot(); expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); - expect(addTransactionFn.mock.calls).toMatchSnapshot(); + expect(addTransactionBatchFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); }); @@ -1976,7 +1995,7 @@ describe('BridgeStatusController', () => { controller.state.txHistory[result.batchId as never], ).toMatchSnapshot(); expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); - expect(addTransactionFn.mock.calls).toMatchSnapshot(); + expect(addTransactionBatchFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); }); @@ -1990,26 +2009,18 @@ describe('BridgeStatusController', () => { await expect( controller.submitTx(quoteWithoutApproval, false), ).rejects.toThrow( - 'Failed to submit cross-chain swap transaction: unknown account in trade data', + 'Failed to submit cross-chain swap batch transaction: unknown account in trade data', ); controller.stopAllPolling(); expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(addTransactionFn).not.toHaveBeenCalled(); + expect(addTransactionBatchFn).not.toHaveBeenCalled(); }); it('should reset USDT allowance', async () => { mockIsEthUsdt.mockReturnValueOnce(true); - // USDT approval reset - mockMessengerCall.mockReturnValueOnce('1'); - setupApprovalMocks(); - - // Approval tx - setupApprovalMocks(); - - // Bridge transaction - setupBridgeMocks(); + setup2ApprovalMocks(); const { controller, startPollingForBridgeTxStatusSpy } = getController(mockMessengerCall); @@ -2022,99 +2033,9 @@ describe('BridgeStatusController', () => { controller.state.txHistory[result.batchId as never], ).toMatchSnapshot(); expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); - expect(addTransactionFn.mock.calls).toMatchSnapshot(); + expect(addTransactionBatchFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); }); - - it('should throw an error if approval tx fails', async () => { - mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); - mockMessengerCall.mockReturnValueOnce('arbitrum-client-id'); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); - estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); - addTransactionFn.mockRejectedValueOnce(new Error('Approval tx failed')); - - const { controller, startPollingForBridgeTxStatusSpy } = - getController(mockMessengerCall); - - await expect( - controller.submitTx(mockEvmQuoteResponse, false), - ).rejects.toThrow('Approval tx failed'); - - expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(addTransactionFn.mock.calls).toMatchSnapshot(); - expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - }); - - it('should throw an error if approval tx meta does not exist', async () => { - mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); - mockMessengerCall.mockReturnValueOnce('arbitrum-client-id'); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); - estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); - addTransactionFn.mockResolvedValueOnce({ - transactionMeta: undefined, - result: new Promise((resolve) => resolve('0xevmTxHash')), - }); - mockMessengerCall.mockReturnValueOnce({ - transactions: [], - }); - - setupBridgeMocks(); - const { controller, startPollingForBridgeTxStatusSpy } = - getController(mockMessengerCall); - - await expect( - controller.submitTx(mockEvmQuoteResponse, false), - ).rejects.toThrow( - 'Failed to submit cross-chain swap tx: txMeta for txHash was not found', - ); - - expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect(addTransactionFn.mock.calls).toMatchSnapshot(); - expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - }); - - it('should delay after submitting linea approval', async () => { - const handleLineaDelaySpy = jest - .spyOn(transactionUtils, 'handleLineaDelay') - .mockResolvedValueOnce(); - const mockTraceFn = jest - .fn() - .mockImplementation((_p, callback) => callback()); - - setupApprovalMocks(); - setupBridgeMocks(); - - const { controller, startPollingForBridgeTxStatusSpy } = getController( - mockMessengerCall, - mockTraceFn, - ); - - const lineaQuoteResponse = { - ...mockEvmQuoteResponse, - quote: { ...mockEvmQuoteResponse.quote, srcChainId: 59144 }, - trade: { - ...(mockEvmQuoteResponse.trade as TxData), - gasLimit: undefined, - } as never, - }; - - const result = await controller.submitTx(lineaQuoteResponse, false); - controller.stopAllPolling(); - - expect(mockTraceFn).toHaveBeenCalledTimes(2); - expect(handleLineaDelaySpy).toHaveBeenCalledTimes(1); - expect(result).toMatchSnapshot(); - expect(startPollingForBridgeTxStatusSpy).toHaveBeenCalledTimes(0); - expect( - controller.state.txHistory[result.batchId as never], - ).toMatchSnapshot(); - expect(mockMessengerCall.mock.calls).toMatchSnapshot(); - expect(mockTraceFn.mock.calls).toMatchSnapshot(); - }); }); describe('submitTx: EVM swap', () => { @@ -2215,12 +2136,12 @@ describe('BridgeStatusController', () => { gasFeeEstimates: { estimatedBaseFee: '0x1234' }, }); estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); - addTransactionFn.mockResolvedValueOnce({ - transactionMeta: mockApprovalTxMeta, - result: Promise.resolve('0xapprovalTxHash'), - }); mockMessengerCall.mockReturnValueOnce({ - transactions: [mockApprovalTxMeta], + gasFeeEstimates: { estimatedBaseFee: '0x1234' }, + }); + estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); + addTransactionBatchFn.mockResolvedValueOnce({ + batchId: '0xapprovalTxHash', }); }; @@ -2231,12 +2152,8 @@ describe('BridgeStatusController', () => { gasFeeEstimates: { estimatedBaseFee: '0x1234' }, }); estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); - addTransactionFn.mockResolvedValueOnce({ - transactionMeta: mockEvmTxMeta, - result: Promise.resolve('0xevmTxHash'), - }); - mockMessengerCall.mockReturnValueOnce({ - transactions: [mockEvmTxMeta], + addTransactionBatchFn.mockResolvedValueOnce({ + batchId: '0xevmTxHash', }); mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); @@ -2244,7 +2161,6 @@ describe('BridgeStatusController', () => { it('should successfully submit an EVM swap transaction with approval', async () => { setupApprovalMocks(); - setupBridgeMocks(); const { controller, startPollingForBridgeTxStatusSpy } = getController(mockMessengerCall); @@ -2256,7 +2172,7 @@ describe('BridgeStatusController', () => { expect( controller.state.txHistory[result.batchId as never], ).toMatchSnapshot(); - expect(addTransactionFn.mock.calls).toMatchSnapshot(); + expect(addTransactionBatchFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); }); @@ -2293,7 +2209,7 @@ describe('BridgeStatusController', () => { controller.state.txHistory[result.batchId as never], ).toMatchSnapshot(); expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); - expect(addTransactionFn.mock.calls).toMatchSnapshot(); + expect(addTransactionBatchFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); }); @@ -2304,9 +2220,8 @@ describe('BridgeStatusController', () => { gasFeeEstimates: { estimatedBaseFee: '0x1234' }, }); estimateGasFeeFn.mockResolvedValueOnce(mockEstimateGasFeeResult); - addTransactionFn.mockResolvedValueOnce({ - transactionMeta: mockEvmTxMeta, - result: Promise.resolve('0xevmTxHash'), + addTransactionBatchFn.mockResolvedValueOnce({ + batchId: '0xevmTxHash', }); // mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); @@ -2322,7 +2237,7 @@ describe('BridgeStatusController', () => { controller.state.txHistory[result.batchId as never], ).toMatchSnapshot(); expect(estimateGasFeeFn.mock.calls).toMatchSnapshot(); - expect(addTransactionFn.mock.calls).toMatchSnapshot(); + expect(addTransactionBatchFn.mock.calls).toMatchSnapshot(); expect(mockMessengerCall.mock.calls).toMatchSnapshot(); }); }); From 007e32e3c65ca028b1b7073b95c1e12d2189b05b Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 1 Jul 2025 17:22:29 -0700 Subject: [PATCH 23/23] chore: add a comment --- .../src/bridge-status-controller.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index dd209c92ccb..eef492ed4de 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -373,13 +373,17 @@ export class BridgeStatusController extends StaticIntervalPollingController { // Use the txMeta.id or batchId as the key so we can reference the txMeta in TransactionController - state.txHistory[txIdToUse] = txHistoryItem; + state.txHistory[txHistoryKey] = txHistoryItem; }); };