diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 5ba155be96..0ca1016952 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -740,11 +740,6 @@ "count": 3 } }, - "packages/bridge-status-controller/src/utils/transaction.ts": { - "no-restricted-syntax": { - "count": 4 - } - }, "packages/chain-agnostic-permission/src/caip25Permission.ts": { "@typescript-eslint/explicit-function-return-type": { "count": 11 @@ -2343,4 +2338,4 @@ "count": 10 } } -} +} \ No newline at end of file diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 2b732e4b5d..695300f67c 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -10,8 +10,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@metamask/bridge-controller` from `^72.0.4` to `^73.0.1` ([#8850](https://github.com/MetaMask/core/pull/8850), [#8866](https://github.com/MetaMask/core/pull/8866)) +- Refactor batch transaction utils to handle multiple quote requests within a batch (for BatchSell integration) ([#8886](https://github.com/MetaMask/core/pull/8886)) + +### Removed + +- Remove unused GasFeeController:getState call result ([#8886](https://github.com/MetaMask/core/pull/8886)) - Remove unnecessary type assertions for bridge quotes ([#8805](https://github.com/MetaMask/core/pull/8805)) +### Fixed + +- Use txFee from the bridge-api whenever it's provided ([#8805](https://github.com/MetaMask/core/pull/8805)) + ## [71.2.0] ### Changed 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 06e65352d1..e4360b51ac 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 @@ -14,7 +14,7 @@ exports[`BridgeStatusController constructor rehydrates the tx history state 1`] "hasApprovalTx": false, "initialDestAssetBalance": undefined, "isStxEnabled": false, - "location": undefined, + "location": "Main View", "originalTransactionId": "bridgeTxMetaId1", "pricingData": { "amountSent": "1.234", @@ -269,7 +269,7 @@ exports[`BridgeStatusController startPollingForBridgeTxStatus sets the inital tx "hasApprovalTx": false, "initialDestAssetBalance": undefined, "isStxEnabled": false, - "location": undefined, + "location": "Main View", "originalTransactionId": "bridgeTxMetaId1", "pricingData": { "amountSent": "1.234", @@ -652,20 +652,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should delay after submitti "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -674,11 +669,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should delay after submitti [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -704,18 +697,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should delay after submitti "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", + "gas": undefined, "to": "0xbridgeContract", "value": "0x0", }, @@ -724,7 +714,6 @@ exports[`BridgeStatusController submitTx: EVM bridge should delay after submitti [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": undefined, @@ -977,20 +966,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should delay after submitti "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -999,11 +983,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should delay after submitti [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -1029,18 +1011,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should delay after submitti "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", + "gas": undefined, "to": "0xbridgeContract", "value": "0x0", }, @@ -1049,7 +1028,6 @@ exports[`BridgeStatusController submitTx: EVM bridge should delay after submitti [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": undefined, @@ -1251,6 +1229,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should handle smart transac [ [ { + "atomic": true, "disable7702": true, "from": "0xaccount1", "isGasFeeIncluded": false, @@ -1269,8 +1248,8 @@ exports[`BridgeStatusController submitTx: EVM bridge should handle smart transac "data": "0xdata", "from": "0xaccount1", "gas": "0x5208", - "maxFeePerGas": "0x0", - "maxPriorityFeePerGas": "0x0", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, "to": "0xbridgeContract", "value": "0x0", }, @@ -1352,9 +1331,6 @@ exports[`BridgeStatusController submitTx: EVM bridge should handle smart transac "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { @@ -1363,7 +1339,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should handle smart transac "transactionParams": { "data": "0xdata", "from": "0xaccount1", - "gas": "21000", + "gas": "0x5208", "to": "0xbridgeContract", "value": "0x0", }, @@ -1604,20 +1580,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobil "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -1626,11 +1597,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobil [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -1656,20 +1625,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobil "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xbridgeContract", "value": "0x0", }, @@ -1678,11 +1642,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobil [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xbridgeContract", @@ -1932,20 +1894,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobil "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -1954,11 +1911,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobil [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -1984,20 +1939,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobil "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xbridgeContract", "value": "0x0", }, @@ -2006,11 +1956,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobil [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xbridgeContract", @@ -2260,20 +2208,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance "NetworkController:findNetworkClientIdByChainId", "0x1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0x1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0x1", "data": "0x095ea7b3000000000000000000000000881d40237659c251811cec9c364ef91dc08d300c0000000000000000000000000000000000000000000000000000000000000000", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -2282,11 +2225,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance [ "TransactionController:addTransaction", { - "chainId": "0x1", "data": "0x095ea7b3000000000000000000000000881d40237659c251811cec9c364ef91dc08d300c0000000000000000000000000000000000000000000000000000000000000000", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -2312,20 +2253,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -2334,11 +2270,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -2364,20 +2298,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xbridgeContract", "value": "0x0", }, @@ -2386,11 +2315,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xbridgeContract", @@ -2615,20 +2542,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -2637,11 +2559,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -2667,20 +2587,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xbridgeContract", "value": "0x0", }, @@ -2689,11 +2604,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xbridgeContract", @@ -2918,20 +2831,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xbridgeContract", "value": "0x0", }, @@ -2940,11 +2848,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xbridgeContract", @@ -3021,20 +2927,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should throw an error if ap "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -3043,11 +2944,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should throw an error if ap [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -3150,20 +3049,15 @@ exports[`BridgeStatusController submitTx: EVM bridge should throw an error if ap "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -3172,11 +3066,9 @@ exports[`BridgeStatusController submitTx: EVM bridge should throw an error if ap [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -3430,20 +3322,15 @@ exports[`BridgeStatusController submitTx: EVM bridge waits for approval tx confi "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -3452,11 +3339,9 @@ exports[`BridgeStatusController submitTx: EVM bridge waits for approval tx confi [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -3482,20 +3367,15 @@ exports[`BridgeStatusController submitTx: EVM bridge waits for approval tx confi "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xbridgeContract", "value": "0x0", }, @@ -3504,11 +3384,9 @@ exports[`BridgeStatusController submitTx: EVM bridge waits for approval tx confi [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xbridgeContract", @@ -3629,20 +3507,15 @@ exports[`BridgeStatusController submitTx: EVM swap should gracefully handle isAt "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -3651,11 +3524,9 @@ exports[`BridgeStatusController submitTx: EVM swap should gracefully handle isAt [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -3681,20 +3552,15 @@ exports[`BridgeStatusController submitTx: EVM swap should gracefully handle isAt "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xbridgeContract", "value": "0x0", }, @@ -3703,11 +3569,9 @@ exports[`BridgeStatusController submitTx: EVM swap should gracefully handle isAt [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xbridgeContract", @@ -3992,6 +3856,7 @@ exports[`BridgeStatusController submitTx: EVM swap should handle smart transacti [ [ { + "atomic": true, "disable7702": true, "from": "0xaccount1", "isGasFeeIncluded": false, @@ -4002,12 +3867,13 @@ exports[`BridgeStatusController submitTx: EVM swap should handle smart transacti "requireApproval": false, "transactions": [ { + "assetsFiatValues": undefined, "params": { "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "maxFeePerGas": "0x0", - "maxPriorityFeePerGas": "0x0", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", "value": "0x0", }, @@ -4022,8 +3888,8 @@ exports[`BridgeStatusController submitTx: EVM swap should handle smart transacti "data": "0xdata", "from": "0xaccount1", "gas": "0x5208", - "maxFeePerGas": "0x0", - "maxPriorityFeePerGas": "0x0", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, "to": "0xbridgeContract", "value": "0x0", }, @@ -4091,9 +3957,6 @@ exports[`BridgeStatusController submitTx: EVM swap should handle smart transacti "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { @@ -4102,15 +3965,12 @@ exports[`BridgeStatusController submitTx: EVM swap should handle smart transacti "transactionParams": { "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, }, ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { @@ -4119,7 +3979,7 @@ exports[`BridgeStatusController submitTx: EVM swap should handle smart transacti "transactionParams": { "data": "0xdata", "from": "0xaccount1", - "gas": "21000", + "gas": "0x5208", "to": "0xbridgeContract", "value": "0x0", }, @@ -4218,20 +4078,15 @@ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum-client-id", "transactionParams": { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xtokenContract", "value": "0x0", }, @@ -4240,11 +4095,9 @@ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xapprovalData", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xtokenContract", @@ -4270,20 +4123,15 @@ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xbridgeContract", "value": "0x0", }, @@ -4292,11 +4140,9 @@ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xbridgeContract", @@ -4517,20 +4363,15 @@ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an "NetworkController:findNetworkClientIdByChainId", "0xa4b1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { "chainId": "0xa4b1", "networkClientId": "arbitrum", "transactionParams": { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", - "gas": "21000", - "gasLimit": "21000", + "gas": "0x5208", "to": "0xbridgeContract", "value": "0x0", }, @@ -4539,11 +4380,9 @@ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an [ "TransactionController:addTransaction", { - "chainId": "0xa4b1", "data": "0xdata", "from": "0xaccount1", "gas": "0x5208", - "gasLimit": "21000", "maxFeePerGas": undefined, "maxPriorityFeePerGas": undefined, "to": "0xbridgeContract", 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 e03bf27b4e..69a9924102 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -17,6 +17,7 @@ import { FeatureId, getQuotesReceivedProperties, UnifiedSwapBridgeEventName, + MetaMetricsSwapsEventSource, } from '@metamask/bridge-controller'; import { Messenger, MOCK_ANY_NAMESPACE } from '@metamask/messenger'; import type { @@ -25,7 +26,10 @@ import type { MockAnyNamespace, } from '@metamask/messenger'; import type { Provider } from '@metamask/network-controller'; -import { CHAIN_IDS } from '@metamask/transaction-controller'; +import { + CHAIN_IDS, + GasFeeEstimateType, +} from '@metamask/transaction-controller'; import { TransactionType, TransactionStatus, @@ -56,6 +60,7 @@ import type { } from './types'; import * as bridgeStatusUtils from './utils/bridge-status'; import * as historyUtils from './utils/history'; +import * as metricsUtils from './utils/metrics'; import * as transactionUtils from './utils/transaction'; type AllBridgeStatusControllerActions = @@ -332,6 +337,7 @@ const getMockStartPollingForBridgeTxStatusArgs = ({ initialDestAssetBalance: undefined, targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', isStxEnabled, + location: MetaMetricsSwapsEventSource.MainView, }); const MockTxHistory = { @@ -432,7 +438,7 @@ const MockTxHistory = { completionTime: undefined, attempts, featureId, - location: undefined, + location: MetaMetricsSwapsEventSource.MainView, }, }), getUnknown: ({ @@ -547,7 +553,7 @@ const MockTxHistory = { isStxEnabled: true, hasApprovalTx: false, attempts: undefined, - location: undefined, + location: MetaMetricsSwapsEventSource.MainView, }, }), }; @@ -580,7 +586,6 @@ function getControllerMessenger( 'TransactionController:isAtomicBatchSupported', 'BridgeController:trackUnifiedSwapBridgeEvent', 'BridgeController:stopPollingForQuotes', - 'GasFeeController:getState', 'RemoteFeatureFlagController:getState', 'AuthenticationController:getBearerToken', 'KeyringController:signTypedMessage', @@ -2831,6 +2836,7 @@ describe('BridgeStatusController', () => { const mockEstimateGasFeeResult = { estimates: { + type: GasFeeEstimateType.FeeMarket, high: { suggestedMaxFeePerGas: '0x1234', suggestedMaxPriorityFeePerGas: '0x5678', @@ -2863,9 +2869,6 @@ describe('BridgeStatusController', () => { const setupApprovalMocks = (mockCall: jest.Mock) => { mockCall.mockReturnValueOnce(mockSelectedAccount); mockCall.mockReturnValueOnce('arbitrum-client-id'); - mockCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); mockMessengerCall.mockResolvedValueOnce({ transactionMeta: mockApprovalTxMeta, @@ -2879,9 +2882,6 @@ describe('BridgeStatusController', () => { const setupBridgeMocks = (mockCall: jest.Mock) => { mockCall.mockReturnValueOnce(mockSelectedAccount); mockCall.mockReturnValueOnce('arbitrum'); - mockCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockCall.mockResolvedValueOnce(mockEstimateGasFeeResult); mockCall.mockResolvedValueOnce({ transactionMeta: mockEvmTxMeta, @@ -2901,9 +2901,6 @@ describe('BridgeStatusController', () => { const setupBridgeStxMocks = (mockCall: jest.Mock) => { mockCall.mockReturnValueOnce(mockSelectedAccount); mockCall.mockReturnValueOnce('arbitrum'); - mockCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockCall.mockResolvedValueOnce(mockEstimateGasFeeResult); addTransactionBatchFn.mockResolvedValueOnce({ batchId: 'batchId1', @@ -3188,17 +3185,8 @@ describe('BridgeStatusController', () => { setupEventTrackingMocks(mockMessengerCall); mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); mockMessengerCall.mockReturnValueOnce('arbitrum'); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); addTransactionBatchFn.mockResolvedValueOnce({ batchId: 'batchId1', @@ -3257,7 +3245,7 @@ describe('BridgeStatusController', () => { action === 'TransactionController:updateTransaction', ), ).toHaveLength(1); - expect(mockMessengerCall).toHaveBeenCalledTimes(14); + expect(mockMessengerCall).toHaveBeenCalledTimes(11); }, ); }); @@ -3266,9 +3254,6 @@ describe('BridgeStatusController', () => { setupEventTrackingMocks(mockMessengerCall); mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); mockMessengerCall.mockReturnValueOnce('arbitrum-client-id'); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); mockMessengerCall.mockRejectedValueOnce(new Error('Approval tx failed')); @@ -3294,9 +3279,6 @@ describe('BridgeStatusController', () => { setupEventTrackingMocks(mockMessengerCall); mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); mockMessengerCall.mockReturnValueOnce('arbitrum-client-id'); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); mockMessengerCall.mockResolvedValueOnce({ transactionMeta: undefined, @@ -3656,11 +3638,9 @@ describe('BridgeStatusController', () => { // Setup for trade tx (no approval) mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); mockMessengerCall.mockReturnValueOnce('arbitrum-client-id'); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce({ estimates: { + type: GasFeeEstimateType.FeeMarket, high: { suggestedMaxFeePerGas: '0x1234', suggestedMaxPriorityFeePerGas: '0x5678', @@ -3831,6 +3811,7 @@ describe('BridgeStatusController', () => { const mockEstimateGasFeeResult = { estimates: { + type: GasFeeEstimateType.FeeMarket, high: { suggestedMaxFeePerGas: '0x1234', suggestedMaxPriorityFeePerGas: '0x5678', @@ -3859,9 +3840,6 @@ describe('BridgeStatusController', () => { const setupApprovalMocks = () => { mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); mockMessengerCall.mockReturnValueOnce('arbitrum-client-id'); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); mockMessengerCall.mockResolvedValueOnce({ transactionMeta: mockApprovalTxMeta, @@ -3875,9 +3853,6 @@ describe('BridgeStatusController', () => { const setupBridgeMocks = () => { mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); mockMessengerCall.mockReturnValueOnce('arbitrum'); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); mockMessengerCall.mockResolvedValueOnce({ transactionMeta: mockEvmTxMeta, @@ -3919,7 +3894,7 @@ describe('BridgeStatusController', () => { ([action]) => action === 'TransactionController:addTransaction', ), ).toHaveLength(2); - expect(mockMessengerCall).toHaveBeenCalledTimes(16); + expect(mockMessengerCall).toHaveBeenCalledTimes(14); }, ); }); @@ -4379,13 +4354,7 @@ describe('BridgeStatusController', () => { setupEventTrackingMocks(mockMessengerCall); mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); mockMessengerCall.mockReturnValueOnce('arbitrum'); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); addTransactionBatchFn.mockResolvedValueOnce({ batchId: 'batchId1', @@ -4505,13 +4474,7 @@ describe('BridgeStatusController', () => { setupEventTrackingMocks(mockMessengerCall); mockMessengerCall.mockReturnValueOnce(mockSelectedAccount); mockMessengerCall.mockReturnValueOnce('arbitrum'); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: { estimatedBaseFee: '0x1234' }, - }); mockMessengerCall.mockResolvedValueOnce(mockEstimateGasFeeResult); addTransactionBatchFn.mockResolvedValueOnce({ batchId: 'batchId1', @@ -4552,7 +4515,7 @@ describe('BridgeStatusController', () => { ), ).toHaveLength(0); expect(addTransactionBatchFn).toHaveBeenCalledTimes(1); - expect(mockMessengerCall).toHaveBeenCalledTimes(12); + expect(mockMessengerCall).toHaveBeenCalledTimes(10); expect( mockCalls.find( ([action, eventName]) => @@ -4628,7 +4591,7 @@ describe('BridgeStatusController', () => { ([action]) => action === 'TransactionController:addTransaction', ), ).toHaveLength(2); - expect(mockMessengerCall).toHaveBeenCalledTimes(16); + expect(mockMessengerCall).toHaveBeenCalledTimes(14); expect(addTransactionBatchFn).not.toHaveBeenCalled(); expect(mockCalls).toMatchSnapshot(); expect(result).toMatchInlineSnapshot(` @@ -5100,6 +5063,81 @@ describe('BridgeStatusController', () => { expect(messengerCallSpy.mock.lastCall).toMatchSnapshot(); }); + it('should use txMeta properties if history item does not exist', () => { + const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); + + const transactionMeta = { + error: { name: 'Error', message: 'tx-error' }, + chainId: CHAIN_IDS.ARBITRUM, + networkClientId: 'eth-id', + time: Date.now(), + txParams: {} as unknown as TransactionParams, + type: TransactionType.bridge, + status: TransactionStatus.failed, + id: 'bridgeTxMetaId1', + }; + const getEVMTxPropertiesFromTransactionMetaSpy = jest + .spyOn(metricsUtils, 'getEVMTxPropertiesFromTransactionMeta') + .mockImplementationOnce(() => { + bridgeStatusController.wipeBridgeStatus({ + address: 'otherAccount', + ignoreNetwork: true, + }); + return metricsUtils.getEVMTxPropertiesFromTransactionMeta( + transactionMeta, + ); + }); + mockMessenger.publish( + 'TransactionController:transactionStatusUpdated', + { + transactionMeta, + }, + ); + + expect(getEVMTxPropertiesFromTransactionMetaSpy).toHaveBeenCalledTimes( + 2, + ); + expect(bridgeStatusController.state.txHistory).toStrictEqual({}); + expect(messengerCallSpy.mock.lastCall).toMatchInlineSnapshot(` + [ + "BridgeController:trackUnifiedSwapBridgeEvent", + "Unified SwapBridge Failed", + { + "account_hardware_type": null, + "action_type": "swapbridge-v1", + "actual_time_minutes": 0, + "chain_id_destination": "eip155:42161", + "chain_id_source": "eip155:42161", + "custom_slippage": false, + "error_message": "Transaction failed. tx-error", + "gas_included": false, + "gas_included_7702": false, + "is_hardware_wallet": false, + "location": "Main View", + "price_impact": 0, + "provider": "", + "quote_vs_execution_ratio": 0, + "quoted_time_minutes": 0, + "quoted_vs_used_gas_ratio": 0, + "security_warnings": [], + "source_transaction": "FAILED", + "stx_enabled": false, + "swap_type": "crosschain", + "token_address_destination": "eip155:42161/slip44:60", + "token_address_source": "eip155:42161/slip44:60", + "token_security_type_destination": null, + "token_symbol_destination": "", + "token_symbol_source": "", + "usd_actual_gas": 0, + "usd_actual_return": 0, + "usd_amount_source": 0, + "usd_quoted_gas": 0, + "usd_quoted_return": 0, + }, + ] + `); + }); + it('should include ab_tests and active_ab_tests from history in tracked event properties', () => { const abTestsTxMetaId = 'bridgeTxMetaIdAbTests'; mockMessenger.call( diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 3265f7c6e3..37fa42145b 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -91,7 +91,6 @@ import { import { handleNonEvmTx } from './utils/snaps'; import { getApprovalTraceParams, getTraceParams } from './utils/trace'; import { - getAddTransactionBatchParams, handleApprovalDelay, handleMobileHardwareWalletDelay, generateActionId, @@ -1014,38 +1013,40 @@ export class BridgeStatusController extends StaticIntervalPollingController[0], - 'messenger' | 'estimateGasFeeFn' - >, - ): Promise<{ + readonly #handleEvmTransactionBatch = async (args: { + requireApproval: boolean; + isDelegatedAccount: boolean; + isBridgeTx: boolean; + quoteResponse: QuoteResponse & QuoteMetadata; + }): Promise<{ approvalMeta?: TransactionMeta; tradeMeta: TransactionMeta; }> => { - const transactionParams = await getAddTransactionBatchParams({ - messenger: this.messenger, - ...args, - }); - - return await addTransactionBatch( + const { tradeMeta, approvalMeta } = await addTransactionBatch( this.messenger, this.#addTransactionBatchFn, - transactionParams, + args.quoteResponse, + args.isBridgeTx, + args.requireApproval, + args.isDelegatedAccount, ); + + if (!tradeMeta) { + throw new Error( + 'Failed to update cross-chain swap transaction batch: tradeMeta not found', + ); + } + return { tradeMeta, approvalMeta }; }; /** @@ -1200,13 +1201,6 @@ export class BridgeStatusController extends StaticIntervalPollingController; export type RefuelStatusResponse = object & StatusResponse; +/** + * This type ties together the quote, its tx params and the submitted txMeta. + * Each trade/approval will have its own QuoteAndTxMetadata object. + */ +export type QuoteAndTxMetadata = { + type: TransactionType; + quoteResponse: QuoteResponse & QuoteMetadata; + /** + * The approval or trade object from the quote response + */ + tx: TxData; + assetsFiatValues?: { sending?: string; receiving?: string }; + /** + * The simulated gas fee limits for the transaction provided by the bridge-api + */ + txFee?: SimulatedGasFeeLimits | TxFeeGasLimits; + /** + * Transaction metadata from the TransactionController after submission + */ + txMeta?: TransactionMeta; +}; + export type BridgeHistoryItem = { txMetaId?: string; // Optional: not available pre-submission or on sync failure actionId?: string; // Only for non-batch EVM transactions @@ -312,7 +337,6 @@ type AllowedActions = | TransactionControllerIsAtomicBatchSupportedAction | BridgeControllerAction | BridgeControllerAction - | GetGasFeeState | AccountsControllerGetAccountByAddressAction | AuthenticationControllerGetBearerTokenAction | KeyringControllerSignTypedMessageAction; diff --git a/packages/bridge-status-controller/src/utils/gas.test.ts b/packages/bridge-status-controller/src/utils/gas.test.ts deleted file mode 100644 index 5967defba9..0000000000 --- a/packages/bridge-status-controller/src/utils/gas.test.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { - BRIDGE_PREFERRED_GAS_ESTIMATE, - TxData, -} from '@metamask/bridge-controller'; -import type { GasFeeState } from '@metamask/gas-fee-controller'; -import type { FeeMarketGasFeeEstimates } from '@metamask/transaction-controller'; -import { GasFeeEstimateLevel } from '@metamask/transaction-controller'; -import { BigNumber } from 'bignumber.js'; - -import { calculateGasFees, getTxGasEstimates } from './transaction'; - -// Mock data -const mockTxGasFeeEstimates = { - type: 'fee-market', - [GasFeeEstimateLevel.Low]: { - maxFeePerGas: '0x1234567890', - maxPriorityFeePerGas: '0x1234567890', - }, - [GasFeeEstimateLevel.Medium]: { - maxFeePerGas: '0x1234567890', - maxPriorityFeePerGas: '0x1234567890', - }, - [GasFeeEstimateLevel.High]: { - maxFeePerGas: '0x1234567890', - maxPriorityFeePerGas: '0x1234567890', - }, -} as FeeMarketGasFeeEstimates; - -const mockNetworkGasFeeEstimates = { - estimatedBaseFee: '0.00000001', -} as GasFeeState['gasFeeEstimates']; - -const mockMessengerCall = jest.fn(); -const mockMessenger = { call: mockMessengerCall }; - -describe('gas calculation utils', () => { - describe('getTxGasEstimates', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should return gas fee estimates with baseAndPriorityFeePerGas when maxPriorityFeePerGas is provided', async () => { - mockMessenger.call.mockReturnValueOnce({ - gasFeeEstimates: mockNetworkGasFeeEstimates, - }); - mockMessenger.call.mockReturnValueOnce({ - estimates: mockTxGasFeeEstimates, - }); - // Call the function - const result = await getTxGasEstimates(mockMessenger, { - txGasFeeEstimates: mockTxGasFeeEstimates, - networkGasFeeEstimates: mockNetworkGasFeeEstimates, - }); - - // Verify the result - expect(result).toStrictEqual({ - baseAndPriorityFeePerGas: new BigNumber('0.00000001', 10) - .times(10 ** 9) - .plus('0x1234567890', 16), - maxFeePerGas: '0x1234567890', - maxPriorityFeePerGas: '0x1234567890', - }); - }); - - it('should handle missing property in txGasFeeEstimates', async () => { - mockMessenger.call.mockReturnValueOnce({ - gasFeeEstimates: { - estimatedBaseFee: '0.00000001', - } as GasFeeState['gasFeeEstimates'], - }); - mockMessenger.call.mockReturnValueOnce({ - estimates: {}, - }); - - const result = await getTxGasEstimates(mockMessenger); - - expect(result).toStrictEqual({ - baseAndPriorityFeePerGas: undefined, - maxFeePerGas: undefined, - maxPriorityFeePerGas: undefined, - }); - }); - - it('should use Bridge preferred gas estimate as gas estimates', async () => { - const estimates = { - type: 'fee-market', - [GasFeeEstimateLevel.Low]: { - maxFeePerGas: '0xLOW', - maxPriorityFeePerGas: '0xLOW_PRIORITY', - }, - [GasFeeEstimateLevel.Medium]: { - maxFeePerGas: '0xMEDIUM', - maxPriorityFeePerGas: '0xMEDIUM_PRIORITY', - }, - [GasFeeEstimateLevel.High]: { - maxFeePerGas: '0xHIGH', - maxPriorityFeePerGas: '0xHIGH_PRIORITY', - }, - } as FeeMarketGasFeeEstimates; - - mockMessenger.call.mockReturnValueOnce({ - gasFeeEstimates: mockNetworkGasFeeEstimates, - }); - mockMessenger.call.mockReturnValueOnce({ - estimates, - }); - - const result = await getTxGasEstimates(mockMessenger, { - txGasFeeEstimates: estimates, - networkGasFeeEstimates: mockNetworkGasFeeEstimates, - }); - - expect(result.maxFeePerGas).toBe( - estimates[BRIDGE_PREFERRED_GAS_ESTIMATE]?.maxFeePerGas, - ); - expect(result.maxPriorityFeePerGas).toBe( - estimates[BRIDGE_PREFERRED_GAS_ESTIMATE]?.maxPriorityFeePerGas, - ); - }); - - it('should use default estimatedBaseFee when not provided in networkGasFeeEstimates', async () => { - // Mock data - mockMessengerCall.mockClear(); - mockMessengerCall.mockReturnValueOnce({ - gasFeeEstimates: {}, - }); - mockMessengerCall.mockResolvedValueOnce({ - estimates: mockTxGasFeeEstimates, - }); - - // Call the function - const result = await getTxGasEstimates(mockMessenger, { - txGasFeeEstimates: mockTxGasFeeEstimates, - networkGasFeeEstimates: {}, - }); - - // Verify the result - expect(result).toStrictEqual({ - baseAndPriorityFeePerGas: new BigNumber('0', 10) - .times(10 ** 9) - .plus('0x1234567890', 16), - maxFeePerGas: '0x1234567890', - maxPriorityFeePerGas: '0x1234567890', - }); - }); - }); - - describe('calculateGasFees', () => { - const mockTrade: TxData = { - chainId: 1, - gasLimit: 1231, - to: '0x1', - data: '0x1', - from: '0x1', - value: '0x1', - }; - - it('should return empty object if gas fields should be skipped (skipGasFields is true)', async () => { - const result = await calculateGasFees( - true, - null as never, - mockTrade, - 'mainnet', - '0x1', - ); - expect(result).toStrictEqual({}); - }); - - it('should txFee when provided', async () => { - const result = await calculateGasFees( - false, - null as never, - mockTrade, - 'mainnet', - '0x1', - { - maxFeePerGas: '0x1234567890', - maxPriorityFeePerGas: '0x1234567890', - }, - ); - expect(result).toStrictEqual({ - maxFeePerGas: '0x1234567890', - maxPriorityFeePerGas: '0x1234567890', - gas: '1231', - }); - }); - - it.each([ - { - gasLimit: 1231, - expectedGas: '0x4cf', - }, - { - gasLimit: null, - expectedGas: '0x0', - }, - ])( - 'should return $expectedGas if trade.gasLimit is $gasLimit', - async ({ gasLimit, expectedGas }) => { - const mockCall = jest.fn().mockReturnValueOnce({ - gasFeeEstimates: { - estimatedBaseFee: '0x1234', - }, - }); - mockCall.mockResolvedValueOnce({ - estimates: { - [GasFeeEstimateLevel.Medium]: { - maxFeePerGas: '0x1234567890', - maxPriorityFeePerGas: '0x1234567890', - }, - }, - }); - const result = await calculateGasFees( - false, - { call: mockCall } as never, - { ...mockTrade, gasLimit }, - 'mainnet', - '0x1', - ); - expect(result).toStrictEqual({ - gas: expectedGas, - maxFeePerGas: '0x1234567890', - maxPriorityFeePerGas: '0x1234567890', - }); - }, - ); - }); -}); diff --git a/packages/bridge-status-controller/src/utils/transaction.test.ts b/packages/bridge-status-controller/src/utils/transaction.test.ts index 34b5baadcc..6776d35e72 100644 --- a/packages/bridge-status-controller/src/utils/transaction.test.ts +++ b/packages/bridge-status-controller/src/utils/transaction.test.ts @@ -11,22 +11,26 @@ import type { TxData, } from '@metamask/bridge-controller'; import { + GasFeeEstimateType, TransactionStatus, TransactionType, } from '@metamask/transaction-controller'; import type { TransactionMeta } from '@metamask/transaction-controller'; import { APPROVAL_DELAY_MS } from '../constants'; -import type { BridgeStatusControllerMessenger } from '../types'; +import type { + BridgeStatusControllerMessenger, + QuoteAndTxMetadata, +} from '../types'; import { getStatusRequestParams } from './bridge-status'; import * as snaps from './snaps'; import { handleApprovalDelay, handleMobileHardwareWalletDelay, getAddTransactionBatchParams, - findAndUpdateTransactionsInBatch, + toQuoteAndTxMetadata, waitForTxConfirmation, - toBatchTxParams, + findAndUpdateTransactionsInBatch, } from './transaction'; describe('Bridge Status Controller Transaction Utils', () => { @@ -1664,26 +1668,6 @@ describe('Bridge Status Controller Transaction Utils', () => { }); }); - describe('toBatchTxParams', () => { - it('should return params without gas if skipGasFields is true', () => { - const mockTrade = { - chainId: 1, - gasLimit: 1231, - to: '0x1', - data: '0x1', - from: '0x1', - value: '0x1', - }; - const result = toBatchTxParams(true, mockTrade as TxData, {}); - expect(result).toStrictEqual({ - data: '0x1', - from: '0x1', - to: '0x1', - value: '0x1', - }); - }); - }); - describe('getAddTransactionBatchParams', () => { let mockMessagingSystem: BridgeStatusControllerMessenger; const mockAccount = { @@ -1726,7 +1710,14 @@ describe('Bridge Status Controller Transaction Utils', () => { [FeeType.METABRIDGE]: { amount: '100000000000000000', }, - txFee: '50000000000000000', + ...(overrides.gasIncluded7702 || overrides.gasIncluded + ? { + txFee: { + maxFeePerGas: '50000000000000000', + maxPriorityFeePerGas: '50000000000000000', + }, + } + : {}), }, gasIncluded: overrides.gasIncluded ?? false, gasIncluded7702: overrides.gasIncluded7702 ?? false, @@ -1745,6 +1736,7 @@ describe('Bridge Status Controller Transaction Utils', () => { to: '0xTokenContract', data: '0xapprovalData', from: '0xUserAddress', + chainId: ChainId.ETH, }, }), ...(overrides.includeResetApproval && { @@ -1752,6 +1744,7 @@ describe('Bridge Status Controller Transaction Utils', () => { to: '0xTokenContract', data: '0xresetData', from: '0xUserAddress', + chainId: ChainId.ETH, }, }), sentAmount: { @@ -1780,24 +1773,6 @@ describe('Bridge Status Controller Transaction Utils', () => { rpcUrl: 'https://mainnet.infura.io/v3/API_KEY', }; } - if (method === 'GasFeeController:getState') { - return { - gasFeeEstimates: { - low: { - suggestedMaxFeePerGas: '20', - suggestedMaxPriorityFeePerGas: '1', - }, - medium: { - suggestedMaxFeePerGas: '30', - suggestedMaxPriorityFeePerGas: '2', - }, - high: { - suggestedMaxFeePerGas: '40', - suggestedMaxPriorityFeePerGas: '3', - }, - }, - }; - } if (method === 'TransactionController:estimateGasFee') { return estimateGasFeeOverrides; } @@ -1816,12 +1791,16 @@ describe('Bridge Status Controller Transaction Utils', () => { includeApproval: true, }); + const tradeData = toQuoteAndTxMetadata({ + quoteResponse: mockQuoteResponse, + isBridgeTx: true, + }); + const result = await getAddTransactionBatchParams({ + tradeData, + requireApproval: false, quoteResponse: mockQuoteResponse, messenger: mockMessagingSystem, - isBridgeTx: true, - trade: mockQuoteResponse.trade, - approval: mockQuoteResponse.approval, }); expect(result.disable7702).toBe(false); @@ -1831,6 +1810,17 @@ describe('Bridge Status Controller Transaction Utils', () => { expect(result.transactions).toHaveLength(2); expect(result.transactions[0].type).toBe(TransactionType.bridgeApproval); expect(result.transactions[1].type).toBe(TransactionType.bridge); + expect(result.transactions[1].params).toMatchInlineSnapshot(` + { + "data": "0xbridgeData", + "from": "0xUserAddress", + "gas": "0x5208", + "maxFeePerGas": "0xb1a2bc2ec50000", + "maxPriorityFeePerGas": "0xb1a2bc2ec50000", + "to": "0xBridgeContract", + "value": "0x1000", + } + `); }); it('should handle gasIncluded7702 flag set to false', async () => { @@ -1838,11 +1828,14 @@ describe('Bridge Status Controller Transaction Utils', () => { gasIncluded7702: false, }); + const tradeData = toQuoteAndTxMetadata({ + quoteResponse: mockQuoteResponse, + isBridgeTx: false, + }); const result = await getAddTransactionBatchParams({ quoteResponse: mockQuoteResponse, messenger: mockMessagingSystem, - isBridgeTx: false, - trade: mockQuoteResponse.trade, + tradeData, }); expect(result.disable7702).toBe(true); @@ -1851,6 +1844,17 @@ describe('Bridge Status Controller Transaction Utils', () => { // Should not use txFee for gas calculation when both gasIncluded and gasIncluded7702 are false expect(result.transactions).toHaveLength(1); expect(result.transactions[0].type).toBe(TransactionType.swap); + expect(result.transactions[0].params).toMatchInlineSnapshot(` + { + "data": "0xbridgeData", + "from": "0xUserAddress", + "gas": "0x5208", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, + "to": "0xBridgeContract", + "value": "0x1000", + } + `); }); it('uses swap approval when approval provided and isBridgeTx is false', async () => { @@ -1858,17 +1862,30 @@ describe('Bridge Status Controller Transaction Utils', () => { includeApproval: true, }); + const tradeData = toQuoteAndTxMetadata({ + quoteResponse: mockQuoteResponse, + isBridgeTx: false, + }); const result = await getAddTransactionBatchParams({ quoteResponse: mockQuoteResponse, messenger: mockMessagingSystem, - isBridgeTx: false, - trade: mockQuoteResponse.trade, - approval: mockQuoteResponse.approval, + tradeData, }); expect(result.transactions).toHaveLength(2); expect(result.transactions[0].type).toBe(TransactionType.swapApproval); expect(result.transactions[1].type).toBe(TransactionType.swap); + expect(result.transactions[1].params).toMatchInlineSnapshot(` + { + "data": "0xbridgeData", + "from": "0xUserAddress", + "gas": "0x5208", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, + "to": "0xBridgeContract", + "value": "0x1000", + } + `); }); it('uses swap approval type for resetApproval when isBridgeTx is false', async () => { @@ -1876,12 +1893,14 @@ describe('Bridge Status Controller Transaction Utils', () => { includeResetApproval: true, }); + const tradeData = toQuoteAndTxMetadata({ + quoteResponse: mockQuoteResponse, + isBridgeTx: false, + }); const result = await getAddTransactionBatchParams({ quoteResponse: mockQuoteResponse, messenger: mockMessagingSystem, - isBridgeTx: false, - trade: mockQuoteResponse.trade, - resetApproval: mockQuoteResponse.resetApproval, + tradeData, }); expect(result.transactions).toHaveLength(2); @@ -1896,12 +1915,15 @@ describe('Bridge Status Controller Transaction Utils', () => { includeResetApproval: true, }); + const tradeData = toQuoteAndTxMetadata({ + quoteResponse: mockQuoteResponse, + isBridgeTx: true, + }); + const result = await getAddTransactionBatchParams({ quoteResponse: mockQuoteResponse, messenger: mockMessagingSystem, - isBridgeTx: true, - trade: mockQuoteResponse.trade, - resetApproval: mockQuoteResponse.resetApproval, + tradeData, }); expect(result.disable7702).toBe(true); @@ -1911,6 +1933,17 @@ describe('Bridge Status Controller Transaction Utils', () => { expect(result.transactions).toHaveLength(2); expect(result.transactions[0].type).toBe(TransactionType.bridgeApproval); expect(result.transactions[1].type).toBe(TransactionType.bridge); + expect(result.transactions[1].params).toMatchInlineSnapshot(` + { + "data": "0xbridgeData", + "from": "0xUserAddress", + "gas": "0x5208", + "maxFeePerGas": "0xb1a2bc2ec50000", + "maxPriorityFeePerGas": "0xb1a2bc2ec50000", + "to": "0xBridgeContract", + "value": "0x1000", + } + `); }); it('should set isGasFeeIncluded to false and set disable7702 to true when gasIncluded7702 is undefined', async () => { @@ -1918,15 +1951,49 @@ describe('Bridge Status Controller Transaction Utils', () => { gasIncluded7702: undefined, }); + const tradeData = toQuoteAndTxMetadata({ + quoteResponse: mockQuoteResponse, + isBridgeTx: false, + }); const result = await getAddTransactionBatchParams({ quoteResponse: mockQuoteResponse, messenger: mockMessagingSystem, - isBridgeTx: false, - trade: mockQuoteResponse.trade, + tradeData, }); expect(result.isGasFeeIncluded).toBe(false); expect(result.disable7702).toBe(true); + expect(result).toMatchInlineSnapshot(` + { + "atomic": true, + "disable7702": true, + "from": "0xUserAddress", + "isGasFeeIncluded": false, + "isGasFeeSponsored": false, + "isInternal": true, + "networkClientId": undefined, + "origin": "metamask", + "requireApproval": false, + "transactions": [ + { + "assetsFiatValues": { + "receiving": "200", + "sending": "100", + }, + "params": { + "data": "0xbridgeData", + "from": "0xUserAddress", + "gas": "0x5208", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, + "to": "0xBridgeContract", + "value": "0x1000", + }, + "type": "swap", + }, + ], + } + `); }); it('should set isGasFeeIncluded to true and disable7702 to false when gasIncluded7702 is true', async () => { @@ -1934,15 +2001,49 @@ describe('Bridge Status Controller Transaction Utils', () => { gasIncluded7702: true, }); + const tradeData = toQuoteAndTxMetadata({ + quoteResponse: mockQuoteResponse, + isBridgeTx: false, + }); const result = await getAddTransactionBatchParams({ quoteResponse: mockQuoteResponse, messenger: mockMessagingSystem, - isBridgeTx: false, - trade: mockQuoteResponse.trade, + tradeData, }); expect(result.isGasFeeIncluded).toBe(true); expect(result.disable7702).toBe(false); + expect(result).toMatchInlineSnapshot(` + { + "atomic": true, + "disable7702": false, + "from": "0xUserAddress", + "isGasFeeIncluded": true, + "isGasFeeSponsored": false, + "isInternal": true, + "networkClientId": undefined, + "origin": "metamask", + "requireApproval": false, + "transactions": [ + { + "assetsFiatValues": { + "receiving": "200", + "sending": "100", + }, + "params": { + "data": "0xbridgeData", + "from": "0xUserAddress", + "gas": "0x5208", + "maxFeePerGas": "0xb1a2bc2ec50000", + "maxPriorityFeePerGas": "0xb1a2bc2ec50000", + "to": "0xBridgeContract", + "value": "0x1000", + }, + "type": "swap", + }, + ], + } + `); }); it('should set isGasFeeIncluded to false and disable7702 to true when gasIncluded7702 is false', async () => { @@ -1950,15 +2051,49 @@ describe('Bridge Status Controller Transaction Utils', () => { gasIncluded7702: false, }); + const tradeData = toQuoteAndTxMetadata({ + quoteResponse: mockQuoteResponse, + isBridgeTx: false, + }); const result = await getAddTransactionBatchParams({ quoteResponse: mockQuoteResponse, messenger: mockMessagingSystem, - isBridgeTx: false, - trade: mockQuoteResponse.trade, + tradeData, }); expect(result.isGasFeeIncluded).toBe(false); expect(result.disable7702).toBe(true); + expect(result).toMatchInlineSnapshot(` + { + "atomic": true, + "disable7702": true, + "from": "0xUserAddress", + "isGasFeeIncluded": false, + "isGasFeeSponsored": false, + "isInternal": true, + "networkClientId": undefined, + "origin": "metamask", + "requireApproval": false, + "transactions": [ + { + "assetsFiatValues": { + "receiving": "200", + "sending": "100", + }, + "params": { + "data": "0xbridgeData", + "from": "0xUserAddress", + "gas": "0x5208", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, + "to": "0xBridgeContract", + "value": "0x1000", + }, + "type": "swap", + }, + ], + } + `); }); it('should enable 7702 but include gas fields when isDelegatedAccount is true and gasIncluded7702 is false', async () => { @@ -1968,6 +2103,7 @@ describe('Bridge Status Controller Transaction Utils', () => { const mockMessenger = createMockMessagingSystem({ estimates: { + type: GasFeeEstimateType.FeeMarket, medium: { maxFeePerGas: '0xabc', maxPriorityFeePerGas: '0xdef', @@ -1976,12 +2112,15 @@ describe('Bridge Status Controller Transaction Utils', () => { }); const callSpy = jest.spyOn(mockMessenger, 'call'); + const tradeData = toQuoteAndTxMetadata({ + quoteResponse: mockQuoteResponse, + isBridgeTx: true, + }); const result = await getAddTransactionBatchParams({ quoteResponse: mockQuoteResponse, messenger: mockMessenger, - isBridgeTx: true, - trade: mockQuoteResponse.trade, isDelegatedAccount: true, + tradeData, }); // 7702 should be enabled for delegated accounts @@ -1998,9 +2137,6 @@ describe('Bridge Status Controller Transaction Utils', () => { "NetworkController:findNetworkClientIdByChainId", "0x1", ], - [ - "GasFeeController:getState", - ], [ "TransactionController:estimateGasFee", { @@ -2009,7 +2145,7 @@ describe('Bridge Status Controller Transaction Utils', () => { "transactionParams": { "data": "0xbridgeData", "from": "0xUserAddress", - "gas": "21000", + "gas": "0x5208", "to": "0xBridgeContract", "value": "0x1000", }, @@ -2019,11 +2155,18 @@ describe('Bridge Status Controller Transaction Utils', () => { `); // Transaction params should include gas fields expect(result.transactions).toHaveLength(1); - expect(result.transactions[0].params).toHaveProperty('gas'); - expect(result.transactions[0].params).toHaveProperty('maxFeePerGas'); - expect(result.transactions[0].params).toHaveProperty( - 'maxPriorityFeePerGas', - ); + // TxFee values from the estimateGasFee call + expect(result.transactions[0].params).toMatchInlineSnapshot(` + { + "data": "0xbridgeData", + "from": "0xUserAddress", + "gas": "0x5208", + "maxFeePerGas": "0xabc", + "maxPriorityFeePerGas": "0xdef", + "to": "0xBridgeContract", + "value": "0x1000", + } + `); }); it('should enable 7702 and omit gas fields when isDelegatedAccount is true and gasIncluded7702 is true', async () => { @@ -2032,12 +2175,17 @@ describe('Bridge Status Controller Transaction Utils', () => { }); const callSpy = jest.spyOn(mockMessagingSystem, 'call'); - const result = await getAddTransactionBatchParams({ + + const tradeData = toQuoteAndTxMetadata({ quoteResponse: mockQuoteResponse, - messenger: mockMessagingSystem, isBridgeTx: true, - trade: mockQuoteResponse.trade, + }); + + const result = await getAddTransactionBatchParams({ + tradeData, + messenger: mockMessagingSystem, isDelegatedAccount: true, + quoteResponse: mockQuoteResponse, }); // 7702 should be enabled @@ -2052,11 +2200,18 @@ describe('Bridge Status Controller Transaction Utils', () => { ).toHaveLength(0); // Transaction params should NOT include gas fields expect(result.transactions).toHaveLength(1); - expect(result.transactions[0].params).not.toHaveProperty('gas'); - expect(result.transactions[0].params).not.toHaveProperty('maxFeePerGas'); - expect(result.transactions[0].params).not.toHaveProperty( - 'maxPriorityFeePerGas', - ); + // These are the txFee values from the quote response + expect(result.transactions[0].params).toMatchInlineSnapshot(` + { + "data": "0xbridgeData", + "from": "0xUserAddress", + "gas": "0x5208", + "maxFeePerGas": "0xb1a2bc2ec50000", + "maxPriorityFeePerGas": "0xb1a2bc2ec50000", + "to": "0xBridgeContract", + "value": "0x1000", + } + `); }); }); @@ -2124,15 +2279,21 @@ describe('Bridge Status Controller Transaction Utils', () => { const mockMessagingSystem = createMockMessagingSystemWithTxs(txs); const callSpy = jest.spyOn(mockMessagingSystem, 'call'); - const txDataByType = { - [TransactionType.swap]: '0xswapData', - [TransactionType.swapApproval]: '0xapprovalData', - }; + const tradeData = [ + { + tx: { data: '0xswapData' }, + type: TransactionType.swap, + }, + { + tx: { data: '0xapprovalData' }, + type: TransactionType.swapApproval, + }, + ] as unknown as QuoteAndTxMetadata[]; findAndUpdateTransactionsInBatch({ messenger: mockMessagingSystem, batchId, - txDataByType, + tradeData, }); expect( @@ -2184,14 +2345,17 @@ describe('Bridge Status Controller Transaction Utils', () => { const mockMessenger = createMockMessagingSystemWithTxs(txs); const callSpy = jest.spyOn(mockMessenger, 'call'); - const txDataByType = { - [TransactionType.swap]: '0xswapData', - }; + const tradeData = [ + { + tx: { data: '0xswapData' }, + type: TransactionType.swap, + }, + ] as unknown as QuoteAndTxMetadata[]; findAndUpdateTransactionsInBatch({ messenger: mockMessenger as unknown as BridgeStatusControllerMessenger, batchId, - txDataByType, + tradeData, }); // Should identify and update 7702 transaction with delegationAddress @@ -2227,14 +2391,17 @@ describe('Bridge Status Controller Transaction Utils', () => { const mockMessenger = createMockMessagingSystemWithTxs(txs); const callSpy = jest.spyOn(mockMessenger, 'call'); - const txDataByType = { - [TransactionType.swapApproval]: '0xapprovalData', - }; + const tradeData = [ + { + tx: { data: '0xapprovalData' }, + type: TransactionType.swapApproval, + }, + ] as unknown as QuoteAndTxMetadata[]; findAndUpdateTransactionsInBatch({ messenger: mockMessenger as unknown as BridgeStatusControllerMessenger, batchId, - txDataByType, + tradeData, }); // Should match 7702 approval transaction by data @@ -2277,15 +2444,21 @@ describe('Bridge Status Controller Transaction Utils', () => { const mockMessenger = createMockMessagingSystemWithTxs(txs); const callSpy = jest.spyOn(mockMessenger, 'call'); - const txDataByType = { - [TransactionType.bridge]: '0xswapData', - [TransactionType.bridgeApproval]: '0xapprovalData', - }; + const tradeData = [ + { + tx: { data: '0xswapData' }, + type: TransactionType.bridge, + }, + { + tx: { data: '0xapprovalData' }, + type: TransactionType.bridgeApproval, + }, + ] as unknown as QuoteAndTxMetadata[]; findAndUpdateTransactionsInBatch({ messenger: mockMessenger as unknown as BridgeStatusControllerMessenger, batchId, - txDataByType, + tradeData, }); // Should update regular transactions by matching data @@ -2334,14 +2507,17 @@ describe('Bridge Status Controller Transaction Utils', () => { const mockMessagingSystem = createMockMessagingSystemWithTxs(txs); const callSpy = jest.spyOn(mockMessagingSystem, 'call'); - const txDataByType = { - [TransactionType.swap]: '0xswapData', - }; + const tradeData = [ + { + tx: { data: '0xswapData' }, + type: TransactionType.swap, + }, + ] as unknown as QuoteAndTxMetadata[]; findAndUpdateTransactionsInBatch({ messenger: mockMessagingSystem, batchId, - txDataByType, + tradeData, }); // Should not update transactions with different batchId @@ -2364,15 +2540,18 @@ describe('Bridge Status Controller Transaction Utils', () => { const mockMessagingSystem = createMockMessagingSystemWithTxs(txs); - const txDataByType = { - [TransactionType.bridge]: '0xbridgeData', - }; + const tradeData = [ + { + tx: { data: '0xbridgeData' }, + type: TransactionType.bridge, + }, + ] as unknown as QuoteAndTxMetadata[]; // Test with bridge transaction — should match batch type for 7702 const result = findAndUpdateTransactionsInBatch({ messenger: mockMessagingSystem, batchId, - txDataByType, + tradeData, }); // Should match since 7702 bridge transactions use batch type @@ -2408,14 +2587,17 @@ describe('Bridge Status Controller Transaction Utils', () => { txs, ) as unknown as BridgeStatusControllerMessenger; - const txDataByType = { - [TransactionType.bridgeApproval]: '0xapprovalData', - }; + const tradeData = [ + { + tx: { data: '0xapprovalData' }, + type: TransactionType.bridgeApproval, + }, + ] as unknown as QuoteAndTxMetadata[]; const result = findAndUpdateTransactionsInBatch({ messenger: mockMessagingSystem, batchId, - txDataByType, + tradeData, }); expect(mockMessagingSystem.call).toHaveBeenCalledWith( diff --git a/packages/bridge-status-controller/src/utils/transaction.ts b/packages/bridge-status-controller/src/utils/transaction.ts index bb8663727b..71a8535a47 100644 --- a/packages/bridge-status-controller/src/utils/transaction.ts +++ b/packages/bridge-status-controller/src/utils/transaction.ts @@ -3,136 +3,76 @@ import { ChainId, formatChainIdToHex, BRIDGE_PREFERRED_GAS_ESTIMATE, + isEvmTxData, + FeeType, } from '@metamask/bridge-controller'; import type { QuoteMetadata, QuoteResponse, + SimulatedGasFeeLimits, + Trade, TxData, + TxFeeGasLimits, } from '@metamask/bridge-controller'; import { toHex } from '@metamask/controller-utils'; import { + GasFeeEstimateType, TransactionStatus, TransactionType, } from '@metamask/transaction-controller'; import type { - BatchTransactionParams, IsAtomicBatchSupportedResultEntry, TransactionController, TransactionMeta, TransactionBatchSingleRequest, - TransactionParams, + BatchTransactionParams, } from '@metamask/transaction-controller'; -import { createProjectLogger, Hex } from '@metamask/utils'; -import { BigNumber } from 'bignumber.js'; +import { createProjectLogger } from '@metamask/utils'; +import type { Hex } from '@metamask/utils'; import { APPROVAL_DELAY_MS } from '../constants'; import type { BridgeStatusControllerMessenger } from '../types'; +import type { QuoteAndTxMetadata } from '../types'; import { getAccountByAddress } from './accounts'; import { getNetworkClientIdByChainId } from './network'; -const isApprovalTx = (type: TransactionType) => +export const isApprovalTx = (type: TransactionType) => type === TransactionType.bridgeApproval || type === TransactionType.swapApproval; -const isTradeTx = (type: TransactionType) => +export const isTradeTx = (type: TransactionType) => type === TransactionType.bridge || type === TransactionType.swap; export const isCrossChainTx = (type: TransactionType) => isTradeTx(type) || isApprovalTx(type); -export const getGasFeeEstimates = async ( - messenger: BridgeStatusControllerMessenger, - args: Parameters[0], -): Promise<{ maxFeePerGas?: string; maxPriorityFeePerGas?: string }> => { - const { estimates } = await messenger.call( - 'TransactionController:estimateGasFee', - args, - ); - if ( - BRIDGE_PREFERRED_GAS_ESTIMATE in estimates && - typeof estimates[BRIDGE_PREFERRED_GAS_ESTIMATE] === 'object' && - 'maxFeePerGas' in estimates[BRIDGE_PREFERRED_GAS_ESTIMATE] && - 'maxPriorityFeePerGas' in estimates[BRIDGE_PREFERRED_GAS_ESTIMATE] - ) { - return estimates[BRIDGE_PREFERRED_GAS_ESTIMATE]; - } - return {}; -}; - /** - * Get the gas fee estimates for a transaction + * For 7702 delegated transactions, check for delegation-specific fields + * These transactions might have authorizationList or delegationAddress * - * @param messenger - The messenger for the gas fee estimates - * @param estimateGasFeeParams - The parameters for the {@link TransactionController.estimateGasFee} method - - * @returns The gas fee estimates for the transaction + * @param tx - The transaction meta + * @returns Whether the transaction is a 7702 transaction */ -export const getTxGasEstimates = async ( - messenger: BridgeStatusControllerMessenger, - estimateGasFeeParams: Parameters[0], -) => { - const { gasFeeEstimates } = messenger.call('GasFeeController:getState'); - const estimatedBaseFee = - 'estimatedBaseFee' in gasFeeEstimates - ? gasFeeEstimates.estimatedBaseFee - : '0'; - - // Get transaction's 1559 gas fee estimates - const { maxFeePerGas, maxPriorityFeePerGas } = await getGasFeeEstimates( - messenger, - estimateGasFeeParams, +const is7702Tx = (tx: TransactionMeta) => { + return ( + (Array.isArray(tx.txParams.authorizationList) && + tx.txParams.authorizationList.length > 0) || + Boolean(tx.delegationAddress) ); - - /** - * @deprecated this is unused - */ - const baseAndPriorityFeePerGas = maxPriorityFeePerGas - ? new BigNumber(estimatedBaseFee, 10) - .times(10 ** 9) - .plus(maxPriorityFeePerGas, 16) - : undefined; - - return { - baseAndPriorityFeePerGas, - maxFeePerGas, - maxPriorityFeePerGas, - }; }; -export const calculateGasFees = async ( - skipGasFields: boolean, +export const getGasFeeEstimates = async ( messenger: BridgeStatusControllerMessenger, - { chainId: _, gasLimit, ...trade }: TxData, - networkClientId: string, - chainId: Hex, - txFee?: { maxFeePerGas: string; maxPriorityFeePerGas: string }, + args: Parameters[0], ) => { - if (skipGasFields) { - return {}; - } - if (txFee) { - return { ...txFee, gas: gasLimit?.toString() }; - } - const transactionParams = { - ...trade, - gas: gasLimit?.toString(), - data: trade.data, - to: trade.to, - value: trade.value, - }; - const { maxFeePerGas, maxPriorityFeePerGas } = await getTxGasEstimates( - messenger, - { - transactionParams, - networkClientId, - chainId, - }, + const { estimates } = await messenger.call( + 'TransactionController:estimateGasFee', + args, ); - const maxGasLimit = toHex(transactionParams.gas ?? 0); - return { - maxFeePerGas, - maxPriorityFeePerGas, - gas: maxGasLimit, - }; + if (estimates?.type === GasFeeEstimateType.FeeMarket) { + return estimates[BRIDGE_PREFERRED_GAS_ESTIMATE]; + } + + return undefined; }; export const getTransactions = (messenger: BridgeStatusControllerMessenger) => { @@ -323,64 +263,120 @@ export const waitForTxConfirmation = async ( } }; -export const toBatchTxParams = ( - skipGasFields: boolean, - { chainId, gasLimit, ...trade }: TxData, - { - maxFeePerGas, - maxPriorityFeePerGas, - gas, - }: { maxFeePerGas?: string; maxPriorityFeePerGas?: string; gas?: string }, -): BatchTransactionParams => { - const params = { +export const toQuoteAndTxMetadata = ({ + quoteResponse, + isBridgeTx, +}: { + quoteResponse: QuoteResponse & QuoteMetadata; + isBridgeTx: boolean; +}) => { + const tradeData: QuoteAndTxMetadata[] = []; + + const approvalTxType = isBridgeTx + ? TransactionType.bridgeApproval + : TransactionType.swapApproval; + + if (quoteResponse.resetApproval) { + tradeData.push({ + quoteResponse, + tx: quoteResponse.resetApproval, + type: approvalTxType, + txFee: quoteResponse.quote.feeData[FeeType.TX_FEE], + }); + } + if (quoteResponse.approval && isEvmTxData(quoteResponse.approval)) { + tradeData.push({ + quoteResponse, + tx: quoteResponse.approval, + type: approvalTxType, + txFee: quoteResponse.quote.feeData[FeeType.TX_FEE], + }); + } + tradeData.push({ + quoteResponse, + tx: quoteResponse.trade as TxData, + type: isBridgeTx ? TransactionType.bridge : TransactionType.swap, + assetsFiatValues: { + sending: quoteResponse.sentAmount?.valueInCurrency?.toString(), + receiving: quoteResponse.toTokenAmount?.valueInCurrency?.toString(), + }, + txFee: quoteResponse.quote.feeData[FeeType.TX_FEE], + }); + + return tradeData; +}; + +/** + * Appends the gas fee estimates for a transaction and normalizes the trade data + * + * @param messenger - The messenger for the gas fee estimates + * @param trade - the trade data to append gas fees to + * @param trade.chainId - ignored, use chainId instead + * @param trade.gasLimit - the gas limit to use for the gas fee estimates + * @param networkClientId - the network client ID to use for the gas fee estimates + * @param chainId - the chain ID to use for the gas fee estimates + * @param simulatedGasFeeLimits - either the txFee from the quote or the simulated gas fee limits for the batch sell + * @returns The gas fee estimates for the transaction + */ +export const toTransactionParams = async ( + messenger: BridgeStatusControllerMessenger, + { chainId: tradeChainId, gasLimit, ...trade }: TxData, + networkClientId: string, + chainId: Hex, + simulatedGasFeeLimits?: SimulatedGasFeeLimits | TxFeeGasLimits, +): Promise => { + const normalizedTrade = { ...trade, data: trade.data, to: trade.to, + from: trade.from, value: trade.value, }; - if (skipGasFields) { - return params; + const transactionParams = { + ...trade, + // Only add gasLimit and gas if they're truthy + gas: gasLimit ? toHex(gasLimit) : undefined, + ...normalizedTrade, + }; + + // Use bridge-api's provided gas fee estimates + if (simulatedGasFeeLimits) { + return { + ...transactionParams, + maxFeePerGas: toHex(simulatedGasFeeLimits.maxFeePerGas), + maxPriorityFeePerGas: toHex(simulatedGasFeeLimits.maxPriorityFeePerGas), + }; } + // Get transaction's 1559 gas fee estimates + const gasFeeEstimates = await getGasFeeEstimates(messenger, { + transactionParams, + networkClientId, + chainId, + }); + return { - ...params, - gas: toHex(gas ?? 0), - maxFeePerGas: toHex(maxFeePerGas ?? 0), - maxPriorityFeePerGas: toHex(maxPriorityFeePerGas ?? 0), + ...transactionParams, + maxFeePerGas: gasFeeEstimates?.maxFeePerGas, + maxPriorityFeePerGas: gasFeeEstimates?.maxPriorityFeePerGas, }; }; export const getAddTransactionBatchParams = async ({ messenger, - isBridgeTx, - approval, - resetApproval, - trade, - quoteResponse: { - quote: { - feeData: { txFee }, - gasIncluded, - gasIncluded7702, - gasSponsored, - }, - sentAmount, - toTokenAmount, - }, + quoteResponse, + tradeData, requireApproval = false, - isDelegatedAccount = false, -}: { + isDelegatedAccount, + ...addTransactionBatchParams +}: Partial[0]> & { messenger: BridgeStatusControllerMessenger; - isBridgeTx: boolean; - trade: TxData; - quoteResponse: Omit & - Partial; - approval?: TxData; - resetApproval?: TxData; + quoteResponse: QuoteResponse & QuoteMetadata; + tradeData: QuoteAndTxMetadata[]; requireApproval?: boolean; isDelegatedAccount?: boolean; -}) => { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const isGasless = gasIncluded || gasIncluded7702; +}): Promise[0]> => { + const trade = tradeData[0].tx; const selectedAccount = getAccountByAddress(messenger, trade.from); if (!selectedAccount) { throw new Error( @@ -390,238 +386,198 @@ export const getAddTransactionBatchParams = async ({ const hexChainId = formatChainIdToHex(trade.chainId); const networkClientId = getNetworkClientIdByChainId(messenger, hexChainId); - // Gas fields should be omitted only when gas is sponsored via 7702 - const skipGasFields = gasIncluded7702 === true; - // Enable 7702 batching when the quote includes gasless 7702 support, - // or when the account is already delegated (to avoid the in-flight - // transaction limit for delegated accounts) - let disable7702 = !skipGasFields && !isDelegatedAccount; - - // For gasless transactions with STX/sendBundle we keep disabling 7702. - if (gasIncluded && !gasIncluded7702) { - disable7702 = true; - } - - const transactions: TransactionBatchSingleRequest[] = []; - if (resetApproval) { - const gasFees = await calculateGasFees( - skipGasFields, - messenger, - resetApproval, - networkClientId, - hexChainId, - isGasless ? txFee : undefined, - ); - transactions.push({ - type: isBridgeTx - ? TransactionType.bridgeApproval - : TransactionType.swapApproval, - params: toBatchTxParams(skipGasFields, resetApproval, gasFees), - }); - } - if (approval) { - const gasFees = await calculateGasFees( - skipGasFields, - messenger, - approval, - networkClientId, - hexChainId, - isGasless ? txFee : undefined, - ); - transactions.push({ - type: isBridgeTx - ? TransactionType.bridgeApproval - : TransactionType.swapApproval, - params: toBatchTxParams(skipGasFields, approval, gasFees), - }); - } - const gasFees = await calculateGasFees( - skipGasFields, - messenger, - trade, - networkClientId, - hexChainId, - isGasless ? txFee : undefined, + const transactions: TransactionBatchSingleRequest[] = await Promise.all( + tradeData.map(async ({ tx, txFee, assetsFiatValues, type }) => ({ + params: await toTransactionParams( + messenger, + tx, + networkClientId, + hexChainId, + txFee, + ), + assetsFiatValues, + type, + })), ); - transactions.push({ - type: isBridgeTx ? TransactionType.bridge : TransactionType.swap, - params: toBatchTxParams(skipGasFields, trade, gasFees), - assetsFiatValues: { - sending: sentAmount?.valueInCurrency?.toString(), - receiving: toTokenAmount?.valueInCurrency?.toString(), - }, - }); - const transactionParams: Parameters< - TransactionController['addTransactionBatch'] - >[0] = { - disable7702, - isGasFeeIncluded: Boolean(gasIncluded7702), - isGasFeeSponsored: Boolean(gasSponsored), + + return { networkClientId, requireApproval, origin: 'metamask', - from: trade.from, + from: selectedAccount.address as Hex, isInternal: true, transactions, + atomic: true, + disable7702: + // Enable 7702 batching when the quote includes gasless 7702 support, + quoteResponse.quote.gasIncluded7702 + ? false + : // or when the account is already delegated (to avoid the in-flight transaction limit for delegated accounts) + !isDelegatedAccount || + // For gasless transactions with STX/sendBundle we keep disabling 7702. + quoteResponse.quote.gasIncluded, + isGasFeeSponsored: Boolean(quoteResponse.quote.gasSponsored), + isGasFeeIncluded: Boolean(quoteResponse.quote.gasIncluded7702), + ...addTransactionBatchParams, }; - - return transactionParams; }; -export const findAndUpdateTransactionsInBatch = ({ +/** + * This is a workaround to update the tx type after submission. Batch txs are submitted with + * the "batch" type, but we need to update to swap/bridge for display purposes. + * + * @param params - The parameters for the transaction search + * @param params.messenger - The messenger to use for the transaction + * @param params.batchId - The batch ID to filter for + * @param params.tradeData - The quote, tx data and type for each transaction in the batch + * @returns A list of transaction metas for each trade in the batch] + * + * @example + * [ + * {...tradeData[0], tradeMeta: TransactionMeta} + * {...tradeData[1], tradeMeta: TransactionMeta} + * {...tradeData[2], tradeMeta: TransactionMeta} + * {...tradeData[3], tradeMeta: TransactionMeta} + * ] + */ +const findAllTransactionsInBatch = ({ messenger, batchId, - txDataByType, + tradeData, }: { messenger: BridgeStatusControllerMessenger; batchId: string; - txDataByType: { [key in TransactionType]?: string }; -}) => { - const txs = getTransactions(messenger); - const txBatch: { - approvalMeta?: TransactionMeta; - tradeMeta?: TransactionMeta; - } = { - approvalMeta: undefined, - tradeMeta: undefined, - }; - - // This is a workaround to update the tx type after the tx is signed - // TODO: remove this once the tx type for batch txs is preserved in the tx controller - const txEntries = Object.entries(txDataByType) as [TransactionType, string][]; - txEntries.forEach(([txType, txData]) => { - // Skip types not present in the batch (e.g. swap entry is undefined for bridge txs) - if (txData === undefined) { - return; - } - - // Find transaction by batchId and either matching data or delegation characteristics - const txMeta = txs.find((tx: TransactionMeta) => { - if (tx.batchId !== batchId) { - return false; - } + tradeData: QuoteAndTxMetadata[]; +}): QuoteAndTxMetadata[] => { + // Filter for transactions with batchId + const txs = getTransactions(messenger).filter( + (tx: TransactionMeta) => tx.batchId === batchId, + ); - // For 7702 delegated transactions, check for delegation-specific fields - // These transactions might have authorizationList or delegationAddress - const is7702Transaction = - (Array.isArray(tx.txParams.authorizationList) && - tx.txParams.authorizationList.length > 0) || - Boolean(tx.delegationAddress); - - if (is7702Transaction) { - // For 7702 transactions, we need to match based on transaction type - // since the data field might be different (batch execute call) - if (isTradeTx(txType) && tx.type === TransactionType.batch) { - return true; + return tradeData.map((tradeWithMetadata) => { + const { tx, type } = tradeWithMetadata; + return { + ...tradeWithMetadata, + txMeta: txs.find((txMeta: TransactionMeta) => { + if (is7702Tx(txMeta)) { + // For 7702 transactions, we need to match based on transaction type + // since the data field might be different (batch execute call) + if (isTradeTx(type) && txMeta.type === TransactionType.batch) { + return true; + } + // Also check if it's an approval transaction for 7702 + if (isApprovalTx(type) && txMeta.txParams.data === tx.data) { + return true; + } } - // Also check if it's an approval transaction for 7702 - if (isApprovalTx(txType) && tx.txParams.data === txData) { + // Default matching logic for non-7702 transactions + if (txMeta.txParams.data === tx.data) { return true; } - } + return false; + }), + }; + }); +}; - // Default matching logic for non-7702 transactions - return tx.txParams.data === txData; - }); +const updateTransactionsInBatch = ({ + messenger, + allTradesWithMetadata, +}: { + messenger: BridgeStatusControllerMessenger; + allTradesWithMetadata: QuoteAndTxMetadata[]; +}) => { + return allTradesWithMetadata.map((tradeWithMetadata) => { + const { txMeta, type } = tradeWithMetadata; if (txMeta) { - const updatedTx = { ...txMeta, type: txType }; + // Update the tx type from batch to swap/bridge updateTransaction( messenger, txMeta, - { type: txType }, - `Update tx type to ${txType}`, + { type }, + `Update tx type to ${type}`, ); - const txTypes = [ - TransactionType.bridgeApproval, - TransactionType.swapApproval, - ] as readonly string[]; - txBatch[txTypes.includes(txType) ? 'approvalMeta' : 'tradeMeta'] = - updatedTx; + const updatedTx = { ...txMeta, type }; + return { ...tradeWithMetadata, txMeta: updatedTx, type }; } - }); - return txBatch; + return tradeWithMetadata; + }); }; -export const addTransactionBatch = async ( - messenger: BridgeStatusControllerMessenger, - addTransactionBatchFn: TransactionController['addTransactionBatch'], - ...args: Parameters -) => { - const txDataByType = { - [TransactionType.bridgeApproval]: args[0].transactions.find( - ({ type }) => type === TransactionType.bridgeApproval, - )?.params.data, - [TransactionType.swapApproval]: args[0].transactions.find( - ({ type }) => type === TransactionType.swapApproval, - )?.params.data, - [TransactionType.bridge]: args[0].transactions.find( - ({ type }) => type === TransactionType.bridge, - )?.params.data, - [TransactionType.swap]: args[0].transactions.find( - ({ type }) => type === TransactionType.swap, - )?.params.data, - }; - - const { batchId } = await addTransactionBatchFn(...args); - - const { approvalMeta, tradeMeta } = findAndUpdateTransactionsInBatch({ +/** + * Finds all transactions in a batch and updates the tx type from batch to swap/bridge + * + * @deprecated use findAllTransactionsInBatch and updateTransactionsInBatch separately instead + * @param options - The parameters for the transaction search + * @param options.messenger - The messenger to use for the transaction + * @param options.batchId - The batch ID to filter for + * @param options.tradeData - The quote, tx data and type for each transaction in the batch + * @returns The quote, tx data and tx metadata for each transaction in the batch + */ +export const findAndUpdateTransactionsInBatch = ({ + messenger, + batchId, + tradeData, +}: { + messenger: BridgeStatusControllerMessenger; + batchId: string; + tradeData: QuoteAndTxMetadata[]; +}) => { + const quoteAndTxMetas = findAllTransactionsInBatch({ messenger, batchId, - txDataByType, + tradeData, }); - if (!tradeMeta) { - throw new Error( - 'Failed to update cross-chain swap transaction batch: tradeMeta not found', - ); - } + const updatedQuoteAndTxMetas = updateTransactionsInBatch({ + messenger, + allTradesWithMetadata: quoteAndTxMetas, + }); - return { approvalMeta, tradeMeta }; + return { + approvalMeta: updatedQuoteAndTxMetas.find( + ({ type, txMeta }) => isApprovalTx(type) && txMeta, + )?.txMeta, + tradeMeta: updatedQuoteAndTxMetas.find( + ({ type, txMeta }) => isTradeTx(type) && txMeta, + )?.txMeta, + }; }; -// TODO rename -const getGasFeesForSubmission = async ( +export const addTransactionBatch = async ( messenger: BridgeStatusControllerMessenger, - transactionParams: TransactionParams, - networkClientId: string, - chainId: Hex, - txFee?: { maxFeePerGas: string; maxPriorityFeePerGas: string }, -): Promise<{ - maxFeePerGas?: string; // Hex - maxPriorityFeePerGas?: string; // Hex - gas?: Hex; -}> => { - const { gas } = transactionParams; - // If txFee is provided (gasIncluded case), use the quote's gas fees - // Convert to hex since txFee values from the quote are decimal strings - if (txFee) { - return { - maxFeePerGas: toHex(txFee.maxFeePerGas), - maxPriorityFeePerGas: toHex(txFee.maxPriorityFeePerGas), - gas: gas ? toHex(gas) : undefined, - }; - } + addTransactionBatchFn: TransactionController['addTransactionBatch'], + quoteResponse: QuoteResponse & QuoteMetadata, + isBridgeTx: boolean, + requireApproval?: boolean, + isDelegatedAccount?: boolean, +) => { + const tradeData = toQuoteAndTxMetadata({ + quoteResponse, + isBridgeTx, + }); - const { maxFeePerGas, maxPriorityFeePerGas } = await getTxGasEstimates( + const transactionParams = await getAddTransactionBatchParams({ + tradeData, + requireApproval, + isDelegatedAccount, messenger, - { - transactionParams, - chainId, - networkClientId, - }, - ); + quoteResponse, + }); - return { - maxFeePerGas, - maxPriorityFeePerGas, - gas: gas ? toHex(gas) : undefined, - }; + const { batchId } = await addTransactionBatchFn(transactionParams); + + return findAndUpdateTransactionsInBatch({ + messenger, + batchId, + tradeData, + }); }; /** - * Submits an EVM transaction to the TransactionController + * Submits an single EVM tx to the TransactionController and returns the txMeta * * @param params - The parameters for the transaction * @param params.transactionType - The type of transaction to submit @@ -667,35 +623,18 @@ export const submitEvmTransaction = async ({ origin: 'metamask', isInternal: true, }; - // Exclude gasLimit from trade to avoid type issues (it can be null) - const { gasLimit: tradeGasLimit, ...tradeWithoutGasLimit } = trade; - - const transactionParams: Parameters< - TransactionController['addTransaction'] - >[0] = { - ...tradeWithoutGasLimit, - chainId: hexChainId, - // Only add gasLimit and gas if they're valid (not undefined/null/zero) - ...(tradeGasLimit && - tradeGasLimit !== 0 && { - gasLimit: tradeGasLimit.toString(), - gas: tradeGasLimit.toString(), - }), - }; - const transactionParamsWithMaxGas: TransactionParams = { - ...transactionParams, - ...(await getGasFeesForSubmission( - messenger, - transactionParams, - networkClientId, - hexChainId, - txFee, - )), - }; + + const transactionParamsWithMaxGas = await toTransactionParams( + messenger, + trade, + networkClientId, + hexChainId, + txFee, + ); return await addTransaction( messenger, - transactionParamsWithMaxGas, + { ...transactionParamsWithMaxGas, from: trade.from }, requestOptions, ); };