@metamask/bridge-status-controller 69.0.0 → 70.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/dist/bridge-status-controller.cjs +115 -354
  3. package/dist/bridge-status-controller.cjs.map +1 -1
  4. package/dist/bridge-status-controller.d.cts +3 -5
  5. package/dist/bridge-status-controller.d.cts.map +1 -1
  6. package/dist/bridge-status-controller.d.mts +3 -5
  7. package/dist/bridge-status-controller.d.mts.map +1 -1
  8. package/dist/bridge-status-controller.intent.cjs +12 -25
  9. package/dist/bridge-status-controller.intent.cjs.map +1 -1
  10. package/dist/bridge-status-controller.intent.d.cts +3 -14
  11. package/dist/bridge-status-controller.intent.d.cts.map +1 -1
  12. package/dist/bridge-status-controller.intent.d.mts +3 -14
  13. package/dist/bridge-status-controller.intent.d.mts.map +1 -1
  14. package/dist/bridge-status-controller.intent.mjs +12 -25
  15. package/dist/bridge-status-controller.intent.mjs.map +1 -1
  16. package/dist/bridge-status-controller.mjs +118 -357
  17. package/dist/bridge-status-controller.mjs.map +1 -1
  18. package/dist/index.cjs +1 -3
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +0 -1
  21. package/dist/index.d.cts.map +1 -1
  22. package/dist/index.d.mts +0 -1
  23. package/dist/index.d.mts.map +1 -1
  24. package/dist/index.mjs +0 -1
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/types.cjs.map +1 -1
  27. package/dist/types.d.cts +11 -5
  28. package/dist/types.d.cts.map +1 -1
  29. package/dist/types.d.mts +11 -5
  30. package/dist/types.d.mts.map +1 -1
  31. package/dist/types.mjs.map +1 -1
  32. package/dist/utils/accounts.cjs +8 -0
  33. package/dist/utils/accounts.cjs.map +1 -0
  34. package/dist/utils/accounts.d.cts +36 -0
  35. package/dist/utils/accounts.d.cts.map +1 -0
  36. package/dist/utils/accounts.d.mts +36 -0
  37. package/dist/utils/accounts.d.mts.map +1 -0
  38. package/dist/utils/accounts.mjs +4 -0
  39. package/dist/utils/accounts.mjs.map +1 -0
  40. package/dist/utils/authentication.cjs +15 -0
  41. package/dist/utils/authentication.cjs.map +1 -0
  42. package/dist/utils/authentication.d.cts +3 -0
  43. package/dist/utils/authentication.d.cts.map +1 -0
  44. package/dist/utils/authentication.d.mts +3 -0
  45. package/dist/utils/authentication.d.mts.map +1 -0
  46. package/dist/utils/authentication.mjs +11 -0
  47. package/dist/utils/authentication.mjs.map +1 -0
  48. package/dist/utils/bridge-status.cjs +17 -1
  49. package/dist/utils/bridge-status.cjs.map +1 -1
  50. package/dist/utils/bridge-status.d.cts +8 -2
  51. package/dist/utils/bridge-status.d.cts.map +1 -1
  52. package/dist/utils/bridge-status.d.mts +8 -2
  53. package/dist/utils/bridge-status.d.mts.map +1 -1
  54. package/dist/utils/bridge-status.mjs +15 -0
  55. package/dist/utils/bridge-status.mjs.map +1 -1
  56. package/dist/utils/bridge.cjs +16 -0
  57. package/dist/utils/bridge.cjs.map +1 -0
  58. package/dist/utils/bridge.d.cts +10 -0
  59. package/dist/utils/bridge.d.cts.map +1 -0
  60. package/dist/utils/bridge.d.mts +10 -0
  61. package/dist/utils/bridge.d.mts.map +1 -0
  62. package/dist/utils/bridge.mjs +11 -0
  63. package/dist/utils/bridge.mjs.map +1 -0
  64. package/dist/utils/gas.cjs +1 -61
  65. package/dist/utils/gas.cjs.map +1 -1
  66. package/dist/utils/gas.d.cts +3 -26
  67. package/dist/utils/gas.d.cts.map +1 -1
  68. package/dist/utils/gas.d.mts +3 -26
  69. package/dist/utils/gas.d.mts.map +1 -1
  70. package/dist/utils/gas.mjs +0 -58
  71. package/dist/utils/gas.mjs.map +1 -1
  72. package/dist/utils/history.cjs +98 -0
  73. package/dist/utils/history.cjs.map +1 -0
  74. package/dist/utils/history.d.cts +22 -0
  75. package/dist/utils/history.d.cts.map +1 -0
  76. package/dist/utils/history.d.mts +22 -0
  77. package/dist/utils/history.d.mts.map +1 -0
  78. package/dist/utils/history.mjs +91 -0
  79. package/dist/utils/history.mjs.map +1 -0
  80. package/dist/utils/intent-api.cjs +41 -26
  81. package/dist/utils/intent-api.cjs.map +1 -1
  82. package/dist/utils/intent-api.d.cts +16 -3
  83. package/dist/utils/intent-api.d.cts.map +1 -1
  84. package/dist/utils/intent-api.d.mts +16 -3
  85. package/dist/utils/intent-api.d.mts.map +1 -1
  86. package/dist/utils/intent-api.mjs +38 -25
  87. package/dist/utils/intent-api.mjs.map +1 -1
  88. package/dist/utils/keyring.cjs +12 -0
  89. package/dist/utils/keyring.cjs.map +1 -0
  90. package/dist/utils/keyring.d.cts +8 -0
  91. package/dist/utils/keyring.d.cts.map +1 -0
  92. package/dist/utils/keyring.d.mts +8 -0
  93. package/dist/utils/keyring.d.mts.map +1 -0
  94. package/dist/utils/keyring.mjs +8 -0
  95. package/dist/utils/keyring.mjs.map +1 -0
  96. package/dist/utils/network.cjs +17 -0
  97. package/dist/utils/network.cjs.map +1 -0
  98. package/dist/utils/network.d.cts +5 -0
  99. package/dist/utils/network.d.cts.map +1 -0
  100. package/dist/utils/network.d.mts +5 -0
  101. package/dist/utils/network.d.mts.map +1 -0
  102. package/dist/utils/network.mjs +12 -0
  103. package/dist/utils/network.mjs.map +1 -0
  104. package/dist/utils/snaps.cjs +146 -1
  105. package/dist/utils/snaps.cjs.map +1 -1
  106. package/dist/utils/snaps.d.cts +48 -0
  107. package/dist/utils/snaps.d.cts.map +1 -1
  108. package/dist/utils/snaps.d.mts +48 -0
  109. package/dist/utils/snaps.d.mts.map +1 -1
  110. package/dist/utils/snaps.mjs +141 -0
  111. package/dist/utils/snaps.mjs.map +1 -1
  112. package/dist/utils/trace.cjs +31 -0
  113. package/dist/utils/trace.cjs.map +1 -0
  114. package/dist/utils/trace.d.cts +17 -0
  115. package/dist/utils/trace.d.cts.map +1 -0
  116. package/dist/utils/trace.d.mts +17 -0
  117. package/dist/utils/trace.d.mts.map +1 -0
  118. package/dist/utils/trace.mjs +26 -0
  119. package/dist/utils/trace.mjs.map +1 -0
  120. package/dist/utils/transaction.cjs +239 -184
  121. package/dist/utils/transaction.cjs.map +1 -1
  122. package/dist/utils/transaction.d.cts +85 -236
  123. package/dist/utils/transaction.d.cts.map +1 -1
  124. package/dist/utils/transaction.d.mts +85 -236
  125. package/dist/utils/transaction.d.mts.map +1 -1
  126. package/dist/utils/transaction.mjs +226 -176
  127. package/dist/utils/transaction.mjs.map +1 -1
  128. package/dist/utils/validators.d.cts +2 -2
  129. package/dist/utils/validators.d.mts +2 -2
  130. package/package.json +3 -3
@@ -1,115 +1,129 @@
1
- import { ChainId, extractTradeData, isTronTrade, formatChainIdToCaip, formatChainIdToHex, isCrossChain } from "@metamask/bridge-controller";
1
+ /* eslint-disable @typescript-eslint/explicit-function-return-type */
2
+ import { ChainId, formatChainIdToHex, BRIDGE_PREFERRED_GAS_ESTIMATE } from "@metamask/bridge-controller";
2
3
  import { toHex } from "@metamask/controller-utils";
3
4
  import { TransactionStatus, TransactionType } from "@metamask/transaction-controller";
4
5
  import { createProjectLogger } from "@metamask/utils";
5
- import { v4 as uuid } from "uuid";
6
- import { calculateGasFees } from "./gas.mjs";
7
- import { createClientTransactionRequest } from "./snaps.mjs";
6
+ import { BigNumber } from "bignumber.js";
7
+ import { getAccountByAddress } from "./accounts.mjs";
8
+ import { getNetworkClientIdByChainId } from "./network.mjs";
8
9
  import { APPROVAL_DELAY_MS } from "../constants.mjs";
9
- export const generateActionId = () => (Date.now() + Math.random()).toString();
10
- export const getStatusRequestParams = (quoteResponse) => {
10
+ export const getGasFeeEstimates = async (messenger, args) => {
11
+ const { estimates } = await messenger.call('TransactionController:estimateGasFee', args);
12
+ if (BRIDGE_PREFERRED_GAS_ESTIMATE in estimates &&
13
+ typeof estimates[BRIDGE_PREFERRED_GAS_ESTIMATE] === 'object' &&
14
+ 'maxFeePerGas' in estimates[BRIDGE_PREFERRED_GAS_ESTIMATE] &&
15
+ 'maxPriorityFeePerGas' in estimates[BRIDGE_PREFERRED_GAS_ESTIMATE]) {
16
+ return estimates[BRIDGE_PREFERRED_GAS_ESTIMATE];
17
+ }
18
+ return {};
19
+ };
20
+ /**
21
+ * Get the gas fee estimates for a transaction
22
+ *
23
+ * @param messenger - The messenger for the gas fee estimates
24
+ * @param estimateGasFeeParams - The parameters for the {@link TransactionController.estimateGasFee} method
25
+
26
+ * @returns The gas fee estimates for the transaction
27
+ */
28
+ export const getTxGasEstimates = async (messenger, estimateGasFeeParams) => {
29
+ const { gasFeeEstimates } = messenger.call('GasFeeController:getState');
30
+ const estimatedBaseFee = 'estimatedBaseFee' in gasFeeEstimates
31
+ ? gasFeeEstimates.estimatedBaseFee
32
+ : '0';
33
+ // Get transaction's 1559 gas fee estimates
34
+ const { maxFeePerGas, maxPriorityFeePerGas } = await getGasFeeEstimates(messenger, estimateGasFeeParams);
35
+ /**
36
+ * @deprecated this is unused
37
+ */
38
+ const baseAndPriorityFeePerGas = maxPriorityFeePerGas
39
+ ? new BigNumber(estimatedBaseFee, 10)
40
+ .times(10 ** 9)
41
+ .plus(maxPriorityFeePerGas, 16)
42
+ : undefined;
11
43
  return {
12
- bridgeId: quoteResponse.quote.bridgeId,
13
- bridge: quoteResponse.quote.bridges[0],
14
- srcChainId: quoteResponse.quote.srcChainId,
15
- destChainId: quoteResponse.quote.destChainId,
16
- quote: quoteResponse.quote,
17
- refuel: Boolean(quoteResponse.quote.refuel),
44
+ baseAndPriorityFeePerGas,
45
+ maxFeePerGas,
46
+ maxPriorityFeePerGas,
18
47
  };
19
48
  };
20
- export const getTxMetaFields = (quoteResponse, approvalTxId) => {
21
- // Handle destination chain ID - should always be convertible for EVM destinations
22
- let destinationChainId;
23
- try {
24
- destinationChainId = formatChainIdToHex(quoteResponse.quote.destChainId);
49
+ export const calculateGasFees = async (skipGasFields, messenger, { chainId: _, gasLimit, ...trade }, networkClientId, chainId, txFee) => {
50
+ if (skipGasFields) {
51
+ return {};
25
52
  }
26
- catch {
27
- // Fallback for non-EVM destination (shouldn't happen for BTC->EVM)
28
- destinationChainId = '0x1'; // Default to mainnet
53
+ if (txFee) {
54
+ return { ...txFee, gas: gasLimit?.toString() };
29
55
  }
56
+ const transactionParams = {
57
+ ...trade,
58
+ gas: gasLimit?.toString(),
59
+ data: trade.data,
60
+ to: trade.to,
61
+ value: trade.value,
62
+ };
63
+ const { maxFeePerGas, maxPriorityFeePerGas } = await getTxGasEstimates(messenger, {
64
+ transactionParams,
65
+ networkClientId,
66
+ chainId,
67
+ });
68
+ const maxGasLimit = toHex(transactionParams.gas ?? 0);
30
69
  return {
31
- destinationChainId,
32
- sourceTokenAmount: quoteResponse.quote.srcTokenAmount,
33
- sourceTokenSymbol: quoteResponse.quote.srcAsset.symbol,
34
- sourceTokenDecimals: quoteResponse.quote.srcAsset.decimals,
35
- sourceTokenAddress: quoteResponse.quote.srcAsset.address,
36
- destinationTokenAmount: quoteResponse.quote.destTokenAmount,
37
- destinationTokenSymbol: quoteResponse.quote.destAsset.symbol,
38
- destinationTokenDecimals: quoteResponse.quote.destAsset.decimals,
39
- destinationTokenAddress: quoteResponse.quote.destAsset.address,
40
- // chainId is now excluded from this function and handled by the caller
41
- approvalTxId,
42
- // this is the decimal (non atomic) amount (not USD value) of source token to swap
43
- swapTokenValue: quoteResponse.sentAmount.amount,
70
+ maxFeePerGas,
71
+ maxPriorityFeePerGas,
72
+ gas: maxGasLimit,
44
73
  };
45
74
  };
46
- /**
47
- * Handles the response from non-EVM transaction submission
48
- * Works with the new unified ClientRequest:signAndSendTransaction interface
49
- * Supports Solana, Bitcoin, and other non-EVM chains
50
- *
51
- * @param snapResponse - The response from the snap after transaction submission
52
- * @param quoteResponse - The quote response containing trade details and metadata
53
- * @param selectedAccount - The selected account information
54
- * @returns The transaction metadata including non-EVM specific fields
55
- */
56
- export const handleNonEvmTxResponse = (snapResponse, quoteResponse, selectedAccount) => {
57
- const selectedAccountAddress = selectedAccount.address;
58
- const snapId = selectedAccount.metadata.snap?.id;
59
- let hash;
60
- // Handle different response formats
61
- if (typeof snapResponse === 'string') {
62
- hash = snapResponse;
63
- }
64
- else if (snapResponse && typeof snapResponse === 'object') {
65
- // Check for new unified interface response format first
66
- if ('transactionId' in snapResponse && snapResponse.transactionId) {
67
- hash = snapResponse.transactionId;
68
- }
69
- else if ('result' in snapResponse &&
70
- snapResponse.result &&
71
- typeof snapResponse.result === 'object') {
72
- // Try to extract signature from common locations in response object
73
- hash =
74
- snapResponse.result.signature ||
75
- snapResponse.result.txid ||
76
- snapResponse.result.hash ||
77
- snapResponse.result.txHash;
78
- }
79
- else if ('signature' in snapResponse &&
80
- snapResponse.signature &&
81
- typeof snapResponse.signature === 'string') {
82
- hash = snapResponse.signature;
83
- }
84
- }
85
- const isBridgeTx = isCrossChain(quoteResponse.quote.srcChainId, quoteResponse.quote.destChainId);
86
- let hexChainId;
75
+ export const getTransactions = (messenger) => {
76
+ return messenger.call('TransactionController:getState').transactions ?? [];
77
+ };
78
+ export const getTransactionMetaById = (messenger, txId) => {
79
+ return getTransactions(messenger).find((tx) => tx.id === txId);
80
+ };
81
+ export const getTransactionMetaByHash = (messenger, txHash) => {
82
+ return getTransactions(messenger).find((tx) => tx.hash === txHash);
83
+ };
84
+ export const updateTransaction = (messenger, txMeta, txMetaUpdates, note) => {
85
+ return messenger.call('TransactionController:updateTransaction', { ...txMeta, ...txMetaUpdates }, note);
86
+ };
87
+ export const checkIsDelegatedAccount = async (messenger, fromAddress, chainIds) => {
87
88
  try {
88
- hexChainId = formatChainIdToHex(quoteResponse.quote.srcChainId);
89
+ const atomicBatchSupport = await messenger.call('TransactionController:isAtomicBatchSupported', {
90
+ address: fromAddress,
91
+ chainIds,
92
+ });
93
+ return atomicBatchSupport.some((entry) => entry.isSupported && entry.delegationAddress);
89
94
  }
90
95
  catch {
91
- // TODO: Fix chain ID activity list handling for Bitcoin
92
- // Fallback to Ethereum mainnet for now
93
- hexChainId = '0x1';
96
+ return false;
94
97
  }
95
- // Extract the transaction data for storage
96
- const tradeData = extractTradeData(quoteResponse.trade);
97
- // Create a transaction meta object with bridge-specific fields
98
- return {
99
- ...getTxMetaFields(quoteResponse),
100
- time: Date.now(),
101
- id: hash ?? uuid(),
102
- chainId: hexChainId,
103
- networkClientId: snapId ?? hexChainId,
104
- txParams: { from: selectedAccountAddress, data: tradeData },
105
- type: isBridgeTx ? TransactionType.bridge : TransactionType.swap,
106
- status: TransactionStatus.submitted,
107
- hash, // Add the transaction signature as hash
108
- origin: snapId,
109
- // Add an explicit flag to mark this as a non-EVM transaction
110
- isSolana: true, // TODO deprecate this and use chainId to detect non-EVM chains
111
- isBridgeTx,
112
- };
98
+ };
99
+ const waitForHashAndReturnFinalTxMeta = async (messenger, hashPromise) => {
100
+ const txHash = await hashPromise;
101
+ const finalTransactionMeta = getTransactionMetaByHash(messenger, txHash);
102
+ if (!finalTransactionMeta) {
103
+ throw new Error('Failed to submit cross-chain swap tx: txMeta for txHash was not found');
104
+ }
105
+ return finalTransactionMeta;
106
+ };
107
+ export const addTransaction = async (messenger, ...args) => {
108
+ const { result } = await messenger.call('TransactionController:addTransaction', ...args);
109
+ return await waitForHashAndReturnFinalTxMeta(messenger, result);
110
+ };
111
+ export const generateActionId = () => (Date.now() + Math.random()).toString();
112
+ /**
113
+ * Adds a synthetic transaction to the TransactionController to display pending intent orders in the UI
114
+ *
115
+ * @param messenger - The messenger to use for the transaction
116
+ * @param args - The arguments for the transaction
117
+ * @returns The transaction meta
118
+ */
119
+ export const addSyntheticTransaction = async (messenger, ...args) => {
120
+ const { transactionMeta } = await messenger.call('TransactionController:addTransaction', args[0], {
121
+ origin: 'metamask',
122
+ actionId: generateActionId(),
123
+ isStateOnly: true,
124
+ ...args[1],
125
+ });
126
+ return transactionMeta;
113
127
  };
114
128
  export const handleApprovalDelay = async (srcChainId) => {
115
129
  if ([ChainId.LINEA, ChainId.BASE].includes(srcChainId)) {
@@ -133,32 +147,20 @@ export const handleMobileHardwareWalletDelay = async (requireApproval) => {
133
147
  }
134
148
  };
135
149
  /**
136
- * Creates a request to sign and send a transaction for non-EVM chains
137
- * Uses the new unified ClientRequest:signAndSendTransaction interface
150
+ * Waits until a given transaction (by id) reaches confirmed/finalized status or fails/times out.
138
151
  *
139
- * @param trade - The trade data
140
- * @param srcChainId - The source chain ID
141
- * @param selectedAccount - The selected account information
142
- * @returns The snap request object for signing and sending transaction
152
+ * @deprecated use addTransaction util
153
+ * @param messenger - the BridgeStatusControllerMessenger
154
+ * @param txId - the transaction ID
155
+ * @param options - the options for the timeout and poll
156
+ * @param options.timeoutMs - the timeout in milliseconds
157
+ * @param options.pollMs - the poll interval in milliseconds
158
+ * @returns the transaction meta
143
159
  */
144
- export const getClientRequest = (trade, srcChainId, selectedAccount) => {
145
- const scope = formatChainIdToCaip(srcChainId);
146
- const transactionData = extractTradeData(trade);
147
- // Tron trades need the visible flag and contract type to be included in the request options
148
- const options = isTronTrade(trade)
149
- ? {
150
- visible: trade.visible,
151
- type: trade.raw_data?.contract?.[0]?.type,
152
- }
153
- : undefined;
154
- // Use the new unified interface
155
- return createClientTransactionRequest(selectedAccount.metadata.snap?.id, transactionData, scope, selectedAccount.id, options);
156
- };
157
160
  export const waitForTxConfirmation = async (messenger, txId, { timeoutMs = 5 * 60000, pollMs = 3000, } = {}) => {
158
161
  const start = Date.now();
159
162
  while (true) {
160
- const { transactions } = messenger.call('TransactionController:getState');
161
- const meta = transactions.find((tx) => tx.id === txId);
163
+ const meta = getTransactionMetaById(messenger, txId);
162
164
  if (meta) {
163
165
  if (meta.status === TransactionStatus.confirmed) {
164
166
  return meta;
@@ -175,26 +177,6 @@ export const waitForTxConfirmation = async (messenger, txId, { timeoutMs = 5 * 6
175
177
  await new Promise((resolve) => setTimeout(resolve, pollMs));
176
178
  }
177
179
  };
178
- export const rekeyHistoryItemInState = (state, actionId, txMeta) => {
179
- const historyItem = state.txHistory[actionId];
180
- if (!historyItem) {
181
- return false;
182
- }
183
- state.txHistory[txMeta.id] = {
184
- ...historyItem,
185
- txMetaId: txMeta.id,
186
- originalTransactionId: historyItem.originalTransactionId ?? txMeta.id,
187
- status: {
188
- ...historyItem.status,
189
- srcChain: {
190
- ...historyItem.status.srcChain,
191
- txHash: txMeta.hash ?? historyItem.status.srcChain?.txHash,
192
- },
193
- },
194
- };
195
- delete state.txHistory[actionId];
196
- return true;
197
- };
198
180
  export const toBatchTxParams = (skipGasFields, { chainId, gasLimit, ...trade }, { maxFeePerGas, maxPriorityFeePerGas, gas, }) => {
199
181
  const params = {
200
182
  ...trade,
@@ -212,23 +194,28 @@ export const toBatchTxParams = (skipGasFields, { chainId, gasLimit, ...trade },
212
194
  maxPriorityFeePerGas: toHex(maxPriorityFeePerGas ?? 0),
213
195
  };
214
196
  };
215
- export const getAddTransactionBatchParams = async ({ messenger, isBridgeTx, approval, resetApproval, trade, quoteResponse: { quote: { feeData: { txFee }, gasIncluded, gasIncluded7702, gasSponsored, }, sentAmount, toTokenAmount, }, requireApproval = false, isDelegatedAccount = false, estimateGasFeeFn, }) => {
197
+ export const getAddTransactionBatchParams = async ({ messenger, isBridgeTx, approval, resetApproval, trade, quoteResponse: { quote: { feeData: { txFee }, gasIncluded, gasIncluded7702, gasSponsored, }, sentAmount, toTokenAmount, }, requireApproval = false, isDelegatedAccount = false, }) => {
198
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
216
199
  const isGasless = gasIncluded || gasIncluded7702;
217
- const selectedAccount = messenger.call('AccountsController:getAccountByAddress', trade.from);
200
+ const selectedAccount = getAccountByAddress(messenger, trade.from);
218
201
  if (!selectedAccount) {
219
202
  throw new Error('Failed to submit cross-chain swap batch transaction: unknown account in trade data');
220
203
  }
221
204
  const hexChainId = formatChainIdToHex(trade.chainId);
222
- const networkClientId = messenger.call('NetworkController:findNetworkClientIdByChainId', hexChainId);
205
+ const networkClientId = getNetworkClientIdByChainId(messenger, hexChainId);
223
206
  // Gas fields should be omitted only when gas is sponsored via 7702
224
207
  const skipGasFields = gasIncluded7702 === true;
225
208
  // Enable 7702 batching when the quote includes gasless 7702 support,
226
209
  // or when the account is already delegated (to avoid the in-flight
227
210
  // transaction limit for delegated accounts)
228
- const disable7702 = !skipGasFields && !isDelegatedAccount;
211
+ let disable7702 = !skipGasFields && !isDelegatedAccount;
212
+ // For gasless transactions with STX/sendBundle we keep disabling 7702.
213
+ if (gasIncluded && !gasIncluded7702) {
214
+ disable7702 = true;
215
+ }
229
216
  const transactions = [];
230
217
  if (resetApproval) {
231
- const gasFees = await calculateGasFees(skipGasFields, messenger, estimateGasFeeFn, resetApproval, networkClientId, hexChainId, isGasless ? txFee : undefined);
218
+ const gasFees = await calculateGasFees(skipGasFields, messenger, resetApproval, networkClientId, hexChainId, isGasless ? txFee : undefined);
232
219
  transactions.push({
233
220
  type: isBridgeTx
234
221
  ? TransactionType.bridgeApproval
@@ -237,7 +224,7 @@ export const getAddTransactionBatchParams = async ({ messenger, isBridgeTx, appr
237
224
  });
238
225
  }
239
226
  if (approval) {
240
- const gasFees = await calculateGasFees(skipGasFields, messenger, estimateGasFeeFn, approval, networkClientId, hexChainId, isGasless ? txFee : undefined);
227
+ const gasFees = await calculateGasFees(skipGasFields, messenger, approval, networkClientId, hexChainId, isGasless ? txFee : undefined);
241
228
  transactions.push({
242
229
  type: isBridgeTx
243
230
  ? TransactionType.bridgeApproval
@@ -245,7 +232,7 @@ export const getAddTransactionBatchParams = async ({ messenger, isBridgeTx, appr
245
232
  params: toBatchTxParams(skipGasFields, approval, gasFees),
246
233
  });
247
234
  }
248
- const gasFees = await calculateGasFees(skipGasFields, messenger, estimateGasFeeFn, trade, networkClientId, hexChainId, isGasless ? txFee : undefined);
235
+ const gasFees = await calculateGasFees(skipGasFields, messenger, trade, networkClientId, hexChainId, isGasless ? txFee : undefined);
249
236
  transactions.push({
250
237
  type: isBridgeTx ? TransactionType.bridge : TransactionType.swap,
251
238
  params: toBatchTxParams(skipGasFields, trade, gasFees),
@@ -266,15 +253,16 @@ export const getAddTransactionBatchParams = async ({ messenger, isBridgeTx, appr
266
253
  };
267
254
  return transactionParams;
268
255
  };
269
- export const findAndUpdateTransactionsInBatch = ({ messenger, updateTransactionFn, batchId, txDataByType, }) => {
270
- const txs = messenger.call('TransactionController:getState').transactions;
256
+ export const findAndUpdateTransactionsInBatch = ({ messenger, batchId, txDataByType, }) => {
257
+ const txs = getTransactions(messenger);
271
258
  const txBatch = {
272
259
  approvalMeta: undefined,
273
260
  tradeMeta: undefined,
274
261
  };
275
262
  // This is a workaround to update the tx type after the tx is signed
276
263
  // TODO: remove this once the tx type for batch txs is preserved in the tx controller
277
- Object.entries(txDataByType).forEach(([txType, txData]) => {
264
+ const txEntries = Object.entries(txDataByType);
265
+ txEntries.forEach(([txType, txData]) => {
278
266
  // Skip types not present in the batch (e.g. swap entry is undefined for bridge txs)
279
267
  if (txData === undefined) {
280
268
  return;
@@ -309,42 +297,104 @@ export const findAndUpdateTransactionsInBatch = ({ messenger, updateTransactionF
309
297
  });
310
298
  if (txMeta) {
311
299
  const updatedTx = { ...txMeta, type: txType };
312
- updateTransactionFn(updatedTx, `Update tx type to ${txType}`);
313
- txBatch[[TransactionType.bridgeApproval, TransactionType.swapApproval].includes(txType)
314
- ? 'approvalMeta'
315
- : 'tradeMeta'] = updatedTx;
300
+ updateTransaction(messenger, txMeta, { type: txType }, `Update tx type to ${txType}`);
301
+ const txTypes = [
302
+ TransactionType.bridgeApproval,
303
+ TransactionType.swapApproval,
304
+ ];
305
+ txBatch[txTypes.includes(txType) ? 'approvalMeta' : 'tradeMeta'] =
306
+ updatedTx;
316
307
  }
317
308
  });
318
309
  return txBatch;
319
310
  };
320
- /**
321
- * Determines the key to use for storing a bridge history item.
322
- * Uses actionId for pre-submission tracking, or bridgeTxMetaId for post-submission.
323
- *
324
- * @param actionId - The action ID used for pre-submission tracking
325
- * @param bridgeTxMetaId - The transaction meta ID from bridgeTxMeta
326
- * @returns The key to use for the history item
327
- * @throws Error if neither actionId nor bridgeTxMetaId is provided
328
- */
329
- export function getHistoryKey(actionId, bridgeTxMetaId) {
330
- const historyKey = actionId ?? bridgeTxMetaId;
331
- if (!historyKey) {
332
- throw new Error('Cannot add tx to history: either actionId or bridgeTxMeta.id must be provided');
311
+ export const addTransactionBatch = async (messenger, addTransactionBatchFn, ...args) => {
312
+ const txDataByType = {
313
+ [TransactionType.bridgeApproval]: args[0].transactions.find(({ type }) => type === TransactionType.bridgeApproval)?.params.data,
314
+ [TransactionType.swapApproval]: args[0].transactions.find(({ type }) => type === TransactionType.swapApproval)?.params.data,
315
+ [TransactionType.bridge]: args[0].transactions.find(({ type }) => type === TransactionType.bridge)?.params.data,
316
+ [TransactionType.swap]: args[0].transactions.find(({ type }) => type === TransactionType.swap)?.params.data,
317
+ };
318
+ const { batchId } = await addTransactionBatchFn(...args);
319
+ const { approvalMeta, tradeMeta } = findAndUpdateTransactionsInBatch({
320
+ messenger,
321
+ batchId,
322
+ txDataByType,
323
+ });
324
+ if (!tradeMeta) {
325
+ throw new Error('Failed to update cross-chain swap transaction batch: tradeMeta not found');
326
+ }
327
+ return { approvalMeta, tradeMeta };
328
+ };
329
+ // TODO rename
330
+ const getGasFeesForSubmission = async (messenger, transactionParams, networkClientId, chainId, txFee) => {
331
+ const { gas } = transactionParams;
332
+ // If txFee is provided (gasIncluded case), use the quote's gas fees
333
+ // Convert to hex since txFee values from the quote are decimal strings
334
+ if (txFee) {
335
+ return {
336
+ maxFeePerGas: toHex(txFee.maxFeePerGas),
337
+ maxPriorityFeePerGas: toHex(txFee.maxPriorityFeePerGas),
338
+ gas: gas ? toHex(gas) : undefined,
339
+ };
333
340
  }
334
- return historyKey;
335
- }
341
+ const { maxFeePerGas, maxPriorityFeePerGas } = await getTxGasEstimates(messenger, {
342
+ transactionParams,
343
+ chainId,
344
+ networkClientId,
345
+ });
346
+ return {
347
+ maxFeePerGas,
348
+ maxPriorityFeePerGas,
349
+ gas: gas ? toHex(gas) : undefined,
350
+ };
351
+ };
336
352
  /**
337
- * Extracts and validates the intent data from a quote response.
353
+ * Submits an EVM transaction to the TransactionController
338
354
  *
339
- * @param quoteResponse - The quote response that may contain intent data
340
- * @returns The intent data from the quote
341
- * @throws Error if the quote does not contain intent data
355
+ * @param params - The parameters for the transaction
356
+ * @param params.transactionType - The type of transaction to submit
357
+ * @param params.trade - The trade data to confirm
358
+ * @param params.requireApproval - Whether to require approval for the transaction
359
+ * @param params.txFee - Optional gas fee parameters from the quote (used when gasIncluded is true)
360
+ * @param params.txFee.maxFeePerGas - The maximum fee per gas from the quote
361
+ * @param params.txFee.maxPriorityFeePerGas - The maximum priority fee per gas from the quote
362
+ * @param params.actionId - Optional actionId for pre-submission history (if not provided, one is generated)
363
+ * @param params.messenger - The messenger to use for the transaction
364
+ * @returns The transaction meta
342
365
  */
343
- export function getIntentFromQuote(quoteResponse) {
344
- const { intent } = quoteResponse.quote;
345
- if (!intent) {
346
- throw new Error('submitIntent: missing intent data');
366
+ export const submitEvmTransaction = async ({ messenger, trade, transactionType, requireApproval = false, txFee,
367
+ // Use provided actionId (for pre-submission history) or generate one
368
+ actionId = generateActionId(), }) => {
369
+ const selectedAccount = getAccountByAddress(messenger, trade.from);
370
+ if (!selectedAccount) {
371
+ throw new Error('Failed to submit cross-chain swap transaction: unknown account in trade data');
347
372
  }
348
- return intent;
349
- }
373
+ const hexChainId = formatChainIdToHex(trade.chainId);
374
+ const networkClientId = getNetworkClientIdByChainId(messenger, hexChainId);
375
+ const requestOptions = {
376
+ actionId,
377
+ networkClientId,
378
+ requireApproval,
379
+ type: transactionType,
380
+ origin: 'metamask',
381
+ };
382
+ // Exclude gasLimit from trade to avoid type issues (it can be null)
383
+ const { gasLimit: tradeGasLimit, ...tradeWithoutGasLimit } = trade;
384
+ const transactionParams = {
385
+ ...tradeWithoutGasLimit,
386
+ chainId: hexChainId,
387
+ // Only add gasLimit and gas if they're valid (not undefined/null/zero)
388
+ ...(tradeGasLimit &&
389
+ tradeGasLimit !== 0 && {
390
+ gasLimit: tradeGasLimit.toString(),
391
+ gas: tradeGasLimit.toString(),
392
+ }),
393
+ };
394
+ const transactionParamsWithMaxGas = {
395
+ ...transactionParams,
396
+ ...(await getGasFeesForSubmission(messenger, transactionParams, networkClientId, hexChainId, txFee)),
397
+ };
398
+ return await addTransaction(messenger, transactionParamsWithMaxGas, requestOptions);
399
+ };
350
400
  //# sourceMappingURL=transaction.mjs.map