@metamask/transaction-controller 15.0.0 → 17.0.0

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 (90) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/dist/TransactionController.d.ts +159 -20
  3. package/dist/TransactionController.d.ts.map +1 -1
  4. package/dist/TransactionController.js +489 -151
  5. package/dist/TransactionController.js.map +1 -1
  6. package/dist/constants.d.ts +2 -3
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/constants.js +3 -15
  9. package/dist/constants.js.map +1 -1
  10. package/dist/{EtherscanRemoteTransactionSource.d.ts → helpers/EtherscanRemoteTransactionSource.d.ts} +1 -1
  11. package/dist/helpers/EtherscanRemoteTransactionSource.d.ts.map +1 -0
  12. package/dist/{EtherscanRemoteTransactionSource.js → helpers/EtherscanRemoteTransactionSource.js} +4 -4
  13. package/dist/helpers/EtherscanRemoteTransactionSource.js.map +1 -0
  14. package/dist/{IncomingTransactionHelper.d.ts → helpers/IncomingTransactionHelper.d.ts} +1 -1
  15. package/dist/helpers/IncomingTransactionHelper.d.ts.map +1 -0
  16. package/dist/{IncomingTransactionHelper.js → helpers/IncomingTransactionHelper.js} +1 -1
  17. package/dist/helpers/IncomingTransactionHelper.js.map +1 -0
  18. package/dist/helpers/PendingTransactionTracker.d.ts +38 -0
  19. package/dist/helpers/PendingTransactionTracker.d.ts.map +1 -0
  20. package/dist/helpers/PendingTransactionTracker.js +329 -0
  21. package/dist/helpers/PendingTransactionTracker.js.map +1 -0
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/logger.d.ts +0 -1
  27. package/dist/logger.d.ts.map +1 -1
  28. package/dist/logger.js +1 -2
  29. package/dist/logger.js.map +1 -1
  30. package/dist/types.d.ts +290 -8
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/types.js +37 -1
  33. package/dist/types.js.map +1 -1
  34. package/dist/utils/etherscan.d.ts.map +1 -0
  35. package/dist/{etherscan.js → utils/etherscan.js} +3 -4
  36. package/dist/utils/etherscan.js.map +1 -0
  37. package/dist/{external-transactions.d.ts → utils/external-transactions.d.ts} +1 -1
  38. package/dist/utils/external-transactions.d.ts.map +1 -0
  39. package/dist/{external-transactions.js → utils/external-transactions.js} +1 -1
  40. package/dist/utils/external-transactions.js.map +1 -0
  41. package/dist/utils/gas-fees.d.ts +31 -0
  42. package/dist/utils/gas-fees.d.ts.map +1 -0
  43. package/dist/utils/gas-fees.js +212 -0
  44. package/dist/utils/gas-fees.js.map +1 -0
  45. package/dist/utils/gas.d.ts +27 -0
  46. package/dist/utils/gas.d.ts.map +1 -0
  47. package/dist/utils/gas.js +134 -0
  48. package/dist/utils/gas.js.map +1 -0
  49. package/dist/{history.d.ts → utils/history.d.ts} +1 -1
  50. package/dist/utils/history.d.ts.map +1 -0
  51. package/dist/utils/history.js.map +1 -0
  52. package/dist/utils/swaps.d.ts +81 -0
  53. package/dist/utils/swaps.d.ts.map +1 -0
  54. package/dist/utils/swaps.js +240 -0
  55. package/dist/utils/swaps.js.map +1 -0
  56. package/dist/{transaction-type.d.ts → utils/transaction-type.d.ts} +1 -1
  57. package/dist/utils/transaction-type.d.ts.map +1 -0
  58. package/dist/{transaction-type.js → utils/transaction-type.js} +1 -1
  59. package/dist/utils/transaction-type.js.map +1 -0
  60. package/dist/{utils.d.ts → utils/utils.d.ts} +13 -3
  61. package/dist/utils/utils.d.ts.map +1 -0
  62. package/dist/{utils.js → utils/utils.js} +21 -5
  63. package/dist/utils/utils.js.map +1 -0
  64. package/dist/{validation.d.ts → utils/validation.d.ts} +1 -1
  65. package/dist/utils/validation.d.ts.map +1 -0
  66. package/dist/{validation.js → utils/validation.js} +90 -7
  67. package/dist/utils/validation.js.map +1 -0
  68. package/package.json +12 -9
  69. package/dist/EtherscanRemoteTransactionSource.d.ts.map +0 -1
  70. package/dist/EtherscanRemoteTransactionSource.js.map +0 -1
  71. package/dist/IncomingTransactionHelper.d.ts.map +0 -1
  72. package/dist/IncomingTransactionHelper.js.map +0 -1
  73. package/dist/PendingTransactionTracker.d.ts +0 -18
  74. package/dist/PendingTransactionTracker.d.ts.map +0 -1
  75. package/dist/PendingTransactionTracker.js +0 -144
  76. package/dist/PendingTransactionTracker.js.map +0 -1
  77. package/dist/etherscan.d.ts.map +0 -1
  78. package/dist/etherscan.js.map +0 -1
  79. package/dist/external-transactions.d.ts.map +0 -1
  80. package/dist/external-transactions.js.map +0 -1
  81. package/dist/history.d.ts.map +0 -1
  82. package/dist/history.js.map +0 -1
  83. package/dist/transaction-type.d.ts.map +0 -1
  84. package/dist/transaction-type.js.map +0 -1
  85. package/dist/utils.d.ts.map +0 -1
  86. package/dist/utils.js.map +0 -1
  87. package/dist/validation.d.ts.map +0 -1
  88. package/dist/validation.js.map +0 -1
  89. /package/dist/{etherscan.d.ts → utils/etherscan.d.ts} +0 -0
  90. /package/dist/{history.js → utils/history.js} +0 -0
@@ -26,16 +26,19 @@ const events_1 = require("events");
26
26
  const lodash_1 = require("lodash");
27
27
  const nonce_tracker_1 = __importDefault(require("nonce-tracker"));
28
28
  const uuid_1 = require("uuid");
29
- const EtherscanRemoteTransactionSource_1 = require("./EtherscanRemoteTransactionSource");
30
- const external_transactions_1 = require("./external-transactions");
31
- const history_1 = require("./history");
32
- const IncomingTransactionHelper_1 = require("./IncomingTransactionHelper");
29
+ const EtherscanRemoteTransactionSource_1 = require("./helpers/EtherscanRemoteTransactionSource");
30
+ const IncomingTransactionHelper_1 = require("./helpers/IncomingTransactionHelper");
31
+ const PendingTransactionTracker_1 = require("./helpers/PendingTransactionTracker");
33
32
  const logger_1 = require("./logger");
34
- const PendingTransactionTracker_1 = require("./PendingTransactionTracker");
35
- const transaction_type_1 = require("./transaction-type");
36
33
  const types_1 = require("./types");
37
- const utils_1 = require("./utils");
38
- const validation_1 = require("./validation");
34
+ const external_transactions_1 = require("./utils/external-transactions");
35
+ const gas_1 = require("./utils/gas");
36
+ const gas_fees_1 = require("./utils/gas-fees");
37
+ const history_1 = require("./utils/history");
38
+ const swaps_1 = require("./utils/swaps");
39
+ const transaction_type_1 = require("./utils/transaction-type");
40
+ const utils_1 = require("./utils/utils");
41
+ const validation_1 = require("./utils/validation");
39
42
  exports.HARDFORK = common_1.Hardfork.London;
40
43
  /**
41
44
  * Multiplier used to determine a transaction's increased gas fee during cancellation
@@ -60,8 +63,11 @@ class TransactionController extends base_controller_1.BaseController {
60
63
  * @param options.blockTracker - The block tracker used to poll for new blocks data.
61
64
  * @param options.disableHistory - Whether to disable storing history in transaction metadata.
62
65
  * @param options.disableSendFlowHistory - Explicitly disable transaction metadata history.
66
+ * @param options.disableSwaps - Whether to disable additional processing on swaps transactions.
67
+ * @param options.getSavedGasFees - Gets the saved gas fee config.
63
68
  * @param options.getCurrentAccountEIP1559Compatibility - Whether or not the account supports EIP-1559.
64
69
  * @param options.getCurrentNetworkEIP1559Compatibility - Whether or not the network supports EIP-1559.
70
+ * @param options.getGasFeeEstimates - Callback to retrieve gas fee estimates.
65
71
  * @param options.getNetworkState - Gets the state of the network controller.
66
72
  * @param options.getPermittedAccounts - Get accounts that a given origin has permissions for.
67
73
  * @param options.getSelectedAddress - Gets the address of the currently selected account.
@@ -72,13 +78,23 @@ class TransactionController extends base_controller_1.BaseController {
72
78
  * @param options.incomingTransactions.updateTransactions - Whether to update local transactions using remote transaction data.
73
79
  * @param options.messenger - The controller messenger.
74
80
  * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.
81
+ * @param options.pendingTransactions - Configuration options for pending transaction support.
82
+ * @param options.pendingTransactions.isResubmitEnabled - Whether transaction publishing is automatically retried.
75
83
  * @param options.provider - The provider used to create the underlying EthQuery instance.
76
84
  * @param options.securityProviderRequest - A function for verifying a transaction, whether it is malicious or not.
85
+ * @param options.hooks - The controller hooks.
86
+ * @param options.hooks.afterSign - Additional logic to execute after signing a transaction. Return false to not change the status to signed.
87
+ * @param options.hooks.beforeApproveOnInit - Additional logic to execute before starting an approval flow for a transaction during initialization. Return false to skip the transaction.
88
+ * @param options.hooks.beforeCheckPendingTransaction - Additional logic to execute before checking pending transactions. Return false to prevent the broadcast of the transaction.
89
+ * @param options.hooks.beforePublish - Additional logic to execute before publishing a transaction. Return false to prevent the broadcast of the transaction.
90
+ * @param options.hooks.getAdditionalSignArguments - Returns additional arguments required to sign a transaction.
77
91
  * @param config - Initial options used to configure this controller.
78
92
  * @param state - Initial state to set on this controller.
79
93
  */
80
- constructor({ blockTracker, disableHistory, disableSendFlowHistory, getCurrentAccountEIP1559Compatibility, getCurrentNetworkEIP1559Compatibility, getNetworkState, getPermittedAccounts, getSelectedAddress, incomingTransactions = {}, messenger, onNetworkStateChange, provider, securityProviderRequest, }, config, state) {
94
+ constructor({ blockTracker, disableHistory, disableSendFlowHistory, disableSwaps, getSavedGasFees, getCurrentAccountEIP1559Compatibility, getCurrentNetworkEIP1559Compatibility, getGasFeeEstimates, getNetworkState, getPermittedAccounts, getSelectedAddress, incomingTransactions = {}, messenger, onNetworkStateChange, pendingTransactions = {}, provider, securityProviderRequest, hooks = {}, }, config, state) {
95
+ var _a, _b, _c, _d, _e;
81
96
  super(config, state);
97
+ this.inProcessOfSigning = new Set();
82
98
  this.mutex = new async_mutex_1.Mutex();
83
99
  /**
84
100
  * EventEmitter instance used to listen to specific transactional events
@@ -100,18 +116,30 @@ class TransactionController extends base_controller_1.BaseController {
100
116
  this.provider = provider;
101
117
  this.messagingSystem = messenger;
102
118
  this.getNetworkState = getNetworkState;
103
- // @ts-expect-error TODO: Provider type alignment
104
119
  this.ethQuery = new eth_query_1.default(provider);
105
120
  this.isSendFlowHistoryDisabled = disableSendFlowHistory !== null && disableSendFlowHistory !== void 0 ? disableSendFlowHistory : false;
106
121
  this.isHistoryDisabled = disableHistory !== null && disableHistory !== void 0 ? disableHistory : false;
122
+ this.isSwapsDisabled = disableSwaps !== null && disableSwaps !== void 0 ? disableSwaps : false;
107
123
  this.registry = new eth_method_registry_1.default({ provider });
124
+ this.getSavedGasFees = getSavedGasFees !== null && getSavedGasFees !== void 0 ? getSavedGasFees : ((_chainId) => undefined);
108
125
  this.getCurrentAccountEIP1559Compatibility =
109
126
  getCurrentAccountEIP1559Compatibility;
110
127
  this.getCurrentNetworkEIP1559Compatibility =
111
128
  getCurrentNetworkEIP1559Compatibility;
129
+ this.getGasFeeEstimates =
130
+ getGasFeeEstimates || (() => Promise.resolve({}));
112
131
  this.getPermittedAccounts = getPermittedAccounts;
113
132
  this.getSelectedAddress = getSelectedAddress;
114
133
  this.securityProviderRequest = securityProviderRequest;
134
+ this.afterSign = (_a = hooks === null || hooks === void 0 ? void 0 : hooks.afterSign) !== null && _a !== void 0 ? _a : (() => true);
135
+ this.beforeApproveOnInit = (_b = hooks === null || hooks === void 0 ? void 0 : hooks.beforeApproveOnInit) !== null && _b !== void 0 ? _b : (() => true);
136
+ this.beforeCheckPendingTransaction =
137
+ (_c = hooks === null || hooks === void 0 ? void 0 : hooks.beforeCheckPendingTransaction) !== null && _c !== void 0 ? _c :
138
+ /* istanbul ignore next */
139
+ (() => true);
140
+ this.beforePublish = (_d = hooks === null || hooks === void 0 ? void 0 : hooks.beforePublish) !== null && _d !== void 0 ? _d : (() => true);
141
+ this.getAdditionalSignArguments =
142
+ (_e = hooks === null || hooks === void 0 ? void 0 : hooks.getAdditionalSignArguments) !== null && _e !== void 0 ? _e : (() => []);
115
143
  this.nonceTracker = new nonce_tracker_1.default({
116
144
  provider,
117
145
  blockTracker,
@@ -134,23 +162,35 @@ class TransactionController extends base_controller_1.BaseController {
134
162
  this.incomingTransactionHelper.hub.on('transactions', this.onIncomingTransactions.bind(this));
135
163
  this.incomingTransactionHelper.hub.on('updatedLastFetchedBlockNumbers', this.onUpdatedLastFetchedBlockNumbers.bind(this));
136
164
  this.pendingTransactionTracker = new PendingTransactionTracker_1.PendingTransactionTracker({
165
+ approveTransaction: this.approveTransaction.bind(this),
137
166
  blockTracker,
138
- failTransaction: this.failTransaction.bind(this),
139
167
  getChainId: this.getChainId.bind(this),
140
168
  getEthQuery: () => this.ethQuery,
141
169
  getTransactions: () => this.state.transactions,
170
+ isResubmitEnabled: pendingTransactions.isResubmitEnabled,
171
+ nonceTracker: this.nonceTracker,
172
+ onStateChange: this.subscribe.bind(this),
173
+ publishTransaction: this.publishTransaction.bind(this),
174
+ hooks: {
175
+ beforeCheckPendingTransaction: this.beforeCheckPendingTransaction.bind(this),
176
+ beforePublish: this.beforePublish.bind(this),
177
+ },
142
178
  });
143
- this.pendingTransactionTracker.hub.on('transactions', this.onPendingTransactionsUpdate.bind(this));
144
- this.pendingTransactionTracker.hub.on('transaction-confirmed', (transactionMeta) => this.hub.emit(`${transactionMeta.id}:confirmed`, transactionMeta));
179
+ this.addPendingTransactionTrackerListeners();
145
180
  onNetworkStateChange(() => {
146
- // @ts-expect-error TODO: Provider type alignment
147
181
  this.ethQuery = new eth_query_1.default(this.provider);
148
182
  this.registry = new eth_method_registry_1.default({ provider: this.provider });
183
+ this.onBootCleanup();
149
184
  });
150
- this.pendingTransactionTracker.start();
185
+ this.onBootCleanup();
151
186
  }
152
- failTransaction(transactionMeta, error) {
153
- const newTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { error, status: types_1.TransactionStatus.failed });
187
+ failTransaction(transactionMeta, error, actionId) {
188
+ const newTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { error: (0, utils_1.normalizeTxError)(error), status: types_1.TransactionStatus.failed });
189
+ this.hub.emit('transaction-failed', {
190
+ actionId,
191
+ error: error.message,
192
+ transactionMeta: newTransactionMeta,
193
+ });
154
194
  this.updateTransaction(newTransactionMeta, 'TransactionController#failTransaction - Add error message and set status to failed');
155
195
  this.hub.emit(`${transactionMeta.id}:finished`, newTransactionMeta);
156
196
  }
@@ -202,12 +242,14 @@ class TransactionController extends base_controller_1.BaseController {
202
242
  * @param opts.securityAlertResponse - Response from security validator.
203
243
  * @param opts.sendFlowHistory - The sendFlowHistory entries to add.
204
244
  * @param opts.type - Type of transaction to add, such as 'cancel' or 'swap'.
245
+ * @param opts.swaps - Options for swaps transactions.
246
+ * @param opts.swaps.hasApproveTx - Whether the transaction has an approval transaction.
247
+ * @param opts.swaps.meta - Metadata for swap transaction.
205
248
  * @returns Object containing a promise resolving to the transaction hash if approved.
206
249
  */
207
- addTransaction(txParams, { actionId, deviceConfirmedOn, method, origin, requireApproval, securityAlertResponse, sendFlowHistory, type, } = {}) {
250
+ addTransaction(txParams, { actionId, deviceConfirmedOn, method, origin, requireApproval, securityAlertResponse, sendFlowHistory, swaps = {}, type, } = {}) {
208
251
  return __awaiter(this, void 0, void 0, function* () {
209
252
  const chainId = this.getChainId();
210
- const { transactions } = this.state;
211
253
  txParams = (0, utils_1.normalizeTxParams)(txParams);
212
254
  const isEIP1559Compatible = yield this.getEIP1559Compatibility();
213
255
  (0, validation_1.validateTxParams)(txParams, isEIP1559Compatible);
@@ -234,16 +276,7 @@ class TransactionController extends base_controller_1.BaseController {
234
276
  verifiedOnBlockchain: false,
235
277
  type: transactionType,
236
278
  };
237
- try {
238
- const { gas, estimateGasError } = yield this.estimateGas(txParams);
239
- txParams.gas = gas;
240
- txParams.estimateGasError = estimateGasError;
241
- transactionMeta.originalGasEstimate = gas;
242
- }
243
- catch (error) {
244
- this.failTransaction(transactionMeta, error);
245
- return Promise.reject(error);
246
- }
279
+ yield this.updateGasProperties(transactionMeta);
247
280
  // Checks if a transaction already exists with a given actionId
248
281
  if (!existingTransactionMeta) {
249
282
  // Set security provider response
@@ -258,16 +291,19 @@ class TransactionController extends base_controller_1.BaseController {
258
291
  if (!this.isHistoryDisabled) {
259
292
  (0, history_1.addInitialHistorySnapshot)(transactionMeta);
260
293
  }
261
- transactions.push(transactionMeta);
262
- this.update({
263
- transactions: this.trimTransactionsForState(transactions),
294
+ yield (0, swaps_1.updateSwapsTransaction)(transactionMeta, transactionType, swaps, {
295
+ isSwapsDisabled: this.isSwapsDisabled,
296
+ cancelTransaction: this.cancelTransaction.bind(this),
297
+ controllerHubEmitter: this.hub.emit.bind(this.hub),
264
298
  });
299
+ this.addMetadata(transactionMeta);
265
300
  this.hub.emit(`unapprovedTransaction`, transactionMeta);
266
301
  }
267
302
  return {
268
303
  result: this.processApproval(transactionMeta, {
269
304
  isExisting: Boolean(existingTransactionMeta),
270
305
  requireApproval,
306
+ actionId,
271
307
  }),
272
308
  transactionMeta,
273
309
  };
@@ -284,22 +320,6 @@ class TransactionController extends base_controller_1.BaseController {
284
320
  yield this.incomingTransactionHelper.update();
285
321
  });
286
322
  }
287
- /**
288
- * Creates approvals for all unapproved transactions persisted.
289
- */
290
- initApprovals() {
291
- const chainId = this.getChainId();
292
- const unapprovedTxs = this.state.transactions.filter((transaction) => transaction.status === types_1.TransactionStatus.unapproved &&
293
- transaction.chainId === chainId);
294
- for (const txMeta of unapprovedTxs) {
295
- this.processApproval(txMeta, {
296
- shouldShowRequest: false,
297
- }).catch((error) => {
298
- /* istanbul ignore next */
299
- console.error('Error during persisted transaction approval', error);
300
- });
301
- }
302
- }
303
323
  /**
304
324
  * Attempts to cancel a transaction based on its ID by setting its status to "rejected"
305
325
  * and emitting a `<tx.id>:finished` hub event.
@@ -307,15 +327,20 @@ class TransactionController extends base_controller_1.BaseController {
307
327
  * @param transactionId - The ID of the transaction to cancel.
308
328
  * @param gasValues - The gas values to use for the cancellation transaction.
309
329
  * @param options - The options for the cancellation transaction.
330
+ * @param options.actionId - Unique ID to prevent duplicate requests.
310
331
  * @param options.estimatedBaseFee - The estimated base fee of the transaction.
311
332
  */
312
- stopTransaction(transactionId, gasValues, { estimatedBaseFee } = {}) {
333
+ stopTransaction(transactionId, gasValues, { estimatedBaseFee, actionId, } = {}) {
313
334
  var _a, _b;
314
335
  return __awaiter(this, void 0, void 0, function* () {
336
+ // If transaction is found for same action id, do not create a cancel transaction.
337
+ if (this.getTransactionWithActionId(actionId)) {
338
+ return;
339
+ }
315
340
  if (gasValues) {
316
341
  (0, utils_1.validateGasValues)(gasValues);
317
342
  }
318
- const transactionMeta = this.state.transactions.find(({ id }) => id === transactionId);
343
+ const transactionMeta = this.getTransaction(transactionId);
319
344
  if (!transactionMeta) {
320
345
  return;
321
346
  }
@@ -342,13 +367,13 @@ class TransactionController extends base_controller_1.BaseController {
342
367
  const newMaxPriorityFeePerGas = (maxPriorityFeePerGasValues &&
343
368
  (0, utils_1.validateMinimumIncrease)(maxPriorityFeePerGasValues, minMaxPriorityFeePerGas)) ||
344
369
  (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);
345
- const txParams = newMaxFeePerGas && newMaxPriorityFeePerGas
370
+ const newTxParams = newMaxFeePerGas && newMaxPriorityFeePerGas
346
371
  ? {
347
372
  from: transactionMeta.txParams.from,
348
373
  gasLimit: transactionMeta.txParams.gas,
349
374
  maxFeePerGas: newMaxFeePerGas,
350
375
  maxPriorityFeePerGas: newMaxPriorityFeePerGas,
351
- type: 2,
376
+ type: '2',
352
377
  nonce: transactionMeta.txParams.nonce,
353
378
  to: transactionMeta.txParams.from,
354
379
  value: '0x0',
@@ -361,14 +386,33 @@ class TransactionController extends base_controller_1.BaseController {
361
386
  to: transactionMeta.txParams.from,
362
387
  value: '0x0',
363
388
  };
364
- const unsignedEthTx = this.prepareUnsignedEthTx(txParams);
389
+ const unsignedEthTx = this.prepareUnsignedEthTx(newTxParams);
365
390
  const signedTx = yield this.sign(unsignedEthTx, transactionMeta.txParams.from);
366
- yield this.updateTransactionMetaRSV(transactionMeta, signedTx);
367
391
  const rawTx = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
368
- yield (0, controller_utils_1.query)(this.ethQuery, 'sendRawTransaction', [rawTx]);
369
- transactionMeta.estimatedBaseFee = estimatedBaseFee;
370
- transactionMeta.status = types_1.TransactionStatus.cancelled;
371
- this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);
392
+ const hash = yield this.publishTransaction(rawTx);
393
+ const cancelTransactionMeta = {
394
+ actionId,
395
+ chainId: transactionMeta.chainId,
396
+ estimatedBaseFee,
397
+ hash,
398
+ id: (0, uuid_1.v1)(),
399
+ originalGasEstimate: transactionMeta.txParams.gas,
400
+ status: types_1.TransactionStatus.submitted,
401
+ time: Date.now(),
402
+ type: types_1.TransactionType.cancel,
403
+ txParams: newTxParams,
404
+ };
405
+ this.addMetadata(cancelTransactionMeta);
406
+ // stopTransaction has no approval request, so we assume the user has already approved the transaction
407
+ this.hub.emit('transaction-approved', {
408
+ transactionMeta: cancelTransactionMeta,
409
+ actionId,
410
+ });
411
+ this.hub.emit('transaction-submitted', {
412
+ transactionMeta: cancelTransactionMeta,
413
+ actionId,
414
+ });
415
+ this.hub.emit(`${cancelTransactionMeta.id}:finished`, cancelTransactionMeta);
372
416
  });
373
417
  }
374
418
  /**
@@ -399,7 +443,6 @@ class TransactionController extends base_controller_1.BaseController {
399
443
  if (!this.sign) {
400
444
  throw new Error('No sign method defined.');
401
445
  }
402
- const { transactions } = this.state;
403
446
  // gasPrice (legacy non EIP1559)
404
447
  const minGasPrice = (0, utils_1.getIncreasedPriceFromExisting)(transactionMeta.txParams.gasPrice, exports.SPEED_UP_RATE);
405
448
  const gasPriceFromValues = (0, utils_1.isGasPriceValue)(gasValues) && gasValues.gasPrice;
@@ -421,18 +464,26 @@ class TransactionController extends base_controller_1.BaseController {
421
464
  (0, utils_1.validateMinimumIncrease)(maxPriorityFeePerGasValues, minMaxPriorityFeePerGas)) ||
422
465
  (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);
423
466
  const txParams = newMaxFeePerGas && newMaxPriorityFeePerGas
424
- ? Object.assign(Object.assign({}, transactionMeta.txParams), { gasLimit: transactionMeta.txParams.gas, maxFeePerGas: newMaxFeePerGas, maxPriorityFeePerGas: newMaxPriorityFeePerGas, type: 2 }) : Object.assign(Object.assign({}, transactionMeta.txParams), { gasLimit: transactionMeta.txParams.gas, gasPrice: newGasPrice });
467
+ ? Object.assign(Object.assign({}, transactionMeta.txParams), { gasLimit: transactionMeta.txParams.gas, maxFeePerGas: newMaxFeePerGas, maxPriorityFeePerGas: newMaxPriorityFeePerGas, type: '2' }) : Object.assign(Object.assign({}, transactionMeta.txParams), { gasLimit: transactionMeta.txParams.gas, gasPrice: newGasPrice });
425
468
  const unsignedEthTx = this.prepareUnsignedEthTx(txParams);
426
469
  const signedTx = yield this.sign(unsignedEthTx, transactionMeta.txParams.from);
427
470
  yield this.updateTransactionMetaRSV(transactionMeta, signedTx);
428
471
  const rawTx = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
429
472
  const hash = yield (0, controller_utils_1.query)(this.ethQuery, 'sendRawTransaction', [rawTx]);
430
473
  const baseTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { estimatedBaseFee, id: (0, uuid_1.v1)(), time: Date.now(), hash,
431
- actionId, originalGasEstimate: transactionMeta.txParams.gas, type: types_1.TransactionType.retry });
474
+ actionId, originalGasEstimate: transactionMeta.txParams.gas, type: types_1.TransactionType.retry, originalType: transactionMeta.type });
432
475
  const newTransactionMeta = newMaxFeePerGas && newMaxPriorityFeePerGas
433
476
  ? Object.assign(Object.assign({}, baseTransactionMeta), { txParams: Object.assign(Object.assign({}, transactionMeta.txParams), { maxFeePerGas: newMaxFeePerGas, maxPriorityFeePerGas: newMaxPriorityFeePerGas }) }) : Object.assign(Object.assign({}, baseTransactionMeta), { txParams: Object.assign(Object.assign({}, transactionMeta.txParams), { gasPrice: newGasPrice }) });
434
- transactions.push(newTransactionMeta);
435
- this.update({ transactions: this.trimTransactionsForState(transactions) });
477
+ this.addMetadata(newTransactionMeta);
478
+ // speedUpTransaction has no approval request, so we assume the user has already approved the transaction
479
+ this.hub.emit('transaction-approved', {
480
+ transactionMeta: newTransactionMeta,
481
+ actionId,
482
+ });
483
+ this.hub.emit('transaction-submitted', {
484
+ transactionMeta: newTransactionMeta,
485
+ actionId,
486
+ });
436
487
  this.hub.emit(`${transactionMeta.id}:speedup`, newTransactionMeta);
437
488
  });
438
489
  }
@@ -444,67 +495,24 @@ class TransactionController extends base_controller_1.BaseController {
444
495
  */
445
496
  estimateGas(transaction) {
446
497
  return __awaiter(this, void 0, void 0, function* () {
447
- const estimatedTransaction = Object.assign({}, transaction);
448
- const { gas, gasPrice: providedGasPrice, to, value, data, } = estimatedTransaction;
449
- const gasPrice = typeof providedGasPrice === 'undefined'
450
- ? yield (0, controller_utils_1.query)(this.ethQuery, 'gasPrice')
451
- : providedGasPrice;
452
- const { providerConfig } = this.getNetworkState();
453
- const isCustomNetwork = providerConfig.type === controller_utils_1.NetworkType.rpc;
454
- // 1. If gas is already defined on the transaction, use it
455
- if (typeof gas !== 'undefined') {
456
- return { gas, gasPrice };
457
- }
458
- const { gasLimit } = yield (0, controller_utils_1.query)(this.ethQuery, 'getBlockByNumber', [
459
- 'latest',
460
- false,
461
- ]);
462
- // 2. If to is not defined or this is not a contract address, and there is no data use 0x5208 / 21000.
463
- // If the network is a custom network then bypass this check and fetch 'estimateGas'.
464
- /* istanbul ignore next */
465
- const code = to ? yield (0, controller_utils_1.query)(this.ethQuery, 'getCode', [to]) : undefined;
466
- /* istanbul ignore next */
467
- if (!isCustomNetwork &&
468
- (!to || (to && !data && (!code || code === '0x')))) {
469
- return { gas: '0x5208', gasPrice };
470
- }
471
- // if data, should be hex string format
472
- estimatedTransaction.data = !data
473
- ? data
474
- : /* istanbul ignore next */ (0, ethereumjs_util_1.addHexPrefix)(data);
475
- // 3. If this is a contract address, safely estimate gas using RPC
476
- estimatedTransaction.value =
477
- typeof value === 'undefined' ? '0x0' : /* istanbul ignore next */ value;
478
- const gasLimitBN = (0, controller_utils_1.hexToBN)(gasLimit);
479
- estimatedTransaction.gas = (0, controller_utils_1.BNToHex)((0, controller_utils_1.fractionBN)(gasLimitBN, 19, 20));
480
- let gasHex;
481
- let estimateGasError;
482
- try {
483
- gasHex = yield (0, controller_utils_1.query)(this.ethQuery, 'estimateGas', [
484
- estimatedTransaction,
485
- ]);
486
- }
487
- catch (error) {
488
- estimateGasError = utils_1.ESTIMATE_GAS_ERROR;
489
- }
490
- // 4. Pad estimated gas without exceeding the most recent block gasLimit. If the network is a
491
- // a custom network then return the eth_estimateGas value.
492
- const gasBN = (0, controller_utils_1.hexToBN)(gasHex);
493
- const maxGasBN = gasLimitBN.muln(0.9);
494
- const paddedGasBN = gasBN.muln(1.5);
495
- /* istanbul ignore next */
496
- if (gasBN.gt(maxGasBN) || isCustomNetwork) {
497
- return { gas: (0, ethereumjs_util_1.addHexPrefix)(gasHex), gasPrice, estimateGasError };
498
- }
499
- /* istanbul ignore next */
500
- if (paddedGasBN.lt(maxGasBN)) {
501
- return {
502
- gas: (0, ethereumjs_util_1.addHexPrefix)((0, controller_utils_1.BNToHex)(paddedGasBN)),
503
- gasPrice,
504
- estimateGasError,
505
- };
506
- }
507
- return { gas: (0, ethereumjs_util_1.addHexPrefix)((0, controller_utils_1.BNToHex)(maxGasBN)), gasPrice, estimateGasError };
498
+ const { estimatedGas, simulationFails } = yield (0, gas_1.estimateGas)(transaction, this.ethQuery);
499
+ return { gas: estimatedGas, simulationFails };
500
+ });
501
+ }
502
+ /**
503
+ * Estimates required gas for a given transaction and add additional gas buffer with the given multiplier.
504
+ *
505
+ * @param transaction - The transaction params to estimate gas for.
506
+ * @param multiplier - The multiplier to use for the gas buffer.
507
+ */
508
+ estimateGasBuffered(transaction, multiplier) {
509
+ return __awaiter(this, void 0, void 0, function* () {
510
+ const { blockGasLimit, estimatedGas, simulationFails } = yield (0, gas_1.estimateGas)(transaction, this.ethQuery);
511
+ const gas = (0, gas_1.addGasBuffer)(estimatedGas, blockGasLimit, multiplier);
512
+ return {
513
+ gas,
514
+ simulationFails,
515
+ };
508
516
  });
509
517
  }
510
518
  /**
@@ -524,6 +532,23 @@ class TransactionController extends base_controller_1.BaseController {
524
532
  transactions[index] = transactionMeta;
525
533
  this.update({ transactions: this.trimTransactionsForState(transactions) });
526
534
  }
535
+ /**
536
+ * Update the security alert response for a transaction.
537
+ *
538
+ * @param transactionId - ID of the transaction.
539
+ * @param securityAlertResponse - The new security alert response for the transaction.
540
+ */
541
+ updateSecurityAlertResponse(transactionId, securityAlertResponse) {
542
+ if (!securityAlertResponse) {
543
+ throw new Error('updateSecurityAlertResponse: securityAlertResponse should not be null');
544
+ }
545
+ const transactionMeta = this.getTransaction(transactionId);
546
+ if (!transactionMeta) {
547
+ throw new Error(`Cannot update security alert response as no transaction metadata found`);
548
+ }
549
+ const updatedMeta = (0, lodash_1.merge)(transactionMeta, { securityAlertResponse });
550
+ this.updateTransaction(updatedMeta, 'TransactionController:updatesecurityAlertResponse - securityAlertResponse updated');
551
+ }
527
552
  /**
528
553
  * Removes all transactions from state, optionally based on the current network.
529
554
  *
@@ -581,6 +606,26 @@ class TransactionController extends base_controller_1.BaseController {
581
606
  this.markNonceDuplicatesDropped(transactionId);
582
607
  // Update external provided transaction with updated gas values and confirmed status.
583
608
  this.updateTransaction(transactionMeta, 'TransactionController:confirmExternalTransaction - Add external transaction');
609
+ if (transactionMeta.type === types_1.TransactionType.swap) {
610
+ (0, swaps_1.updatePostTransactionBalance)(transactionMeta, {
611
+ ethQuery: this.ethQuery,
612
+ getTransaction: this.getTransaction.bind(this),
613
+ updateTransaction: this.updateTransaction.bind(this),
614
+ })
615
+ .then(({ updatedTransactionMeta, approvalTransactionMeta }) => {
616
+ this.hub.emit('post-transaction-balance-updated', {
617
+ transactionMeta: updatedTransactionMeta,
618
+ approvalTransactionMeta,
619
+ });
620
+ })
621
+ .catch((error) => {
622
+ /* istanbul ignore next */
623
+ (0, logger_1.projectLogger)('Error while updating post transaction balance', error);
624
+ });
625
+ }
626
+ this.hub.emit('transaction-confirmed', {
627
+ transactionMeta,
628
+ });
584
629
  }
585
630
  catch (error) {
586
631
  console.error(error);
@@ -662,7 +707,250 @@ class TransactionController extends base_controller_1.BaseController {
662
707
  this.updateTransaction(updatedMeta, 'TransactionController:updateTransactionGasFees - gas values updated');
663
708
  return this.getTransaction(transactionId);
664
709
  }
665
- processApproval(transactionMeta, { isExisting = false, requireApproval, shouldShowRequest = true, }) {
710
+ /**
711
+ * Update the previous gas values of a transaction.
712
+ *
713
+ * @param transactionId - The ID of the transaction to update.
714
+ * @param previousGas - Previous gas values to update.
715
+ * @param previousGas.gasLimit - Maxmimum number of units of gas to use for this transaction.
716
+ * @param previousGas.maxFeePerGas - Maximum amount per gas to pay for the transaction, including the priority fee.
717
+ * @param previousGas.maxPriorityFeePerGas - Maximum amount per gas to give to validator as incentive.
718
+ * @returns The updated transactionMeta.
719
+ */
720
+ updatePreviousGasParams(transactionId, { gasLimit, maxFeePerGas, maxPriorityFeePerGas, }) {
721
+ const transactionMeta = this.getTransaction(transactionId);
722
+ if (!transactionMeta) {
723
+ throw new Error(`Cannot update transaction as no transaction metadata found`);
724
+ }
725
+ (0, utils_1.validateIfTransactionUnapproved)(transactionMeta, 'updatePreviousGasParams');
726
+ const transactionPreviousGas = {
727
+ previousGas: {
728
+ gasLimit,
729
+ maxFeePerGas,
730
+ maxPriorityFeePerGas,
731
+ },
732
+ };
733
+ // only update what is defined
734
+ transactionPreviousGas.previousGas = (0, lodash_1.pickBy)(transactionPreviousGas.previousGas);
735
+ // merge updated previous gas values with existing transaction meta
736
+ const updatedMeta = (0, lodash_1.merge)(transactionMeta, transactionPreviousGas);
737
+ this.updateTransaction(updatedMeta, 'TransactionController:updatePreviousGasParams - Previous gas values updated');
738
+ return this.getTransaction(transactionId);
739
+ }
740
+ /**
741
+ * Gets the next nonce according to the nonce-tracker.
742
+ * Ensure `releaseLock` is called once processing of the `nonce` value is complete.
743
+ *
744
+ * @param address - The hex string address for the transaction.
745
+ * @returns object with the `nextNonce` `nonceDetails`, and the releaseLock.
746
+ */
747
+ getNonceLock(address) {
748
+ return __awaiter(this, void 0, void 0, function* () {
749
+ return this.nonceTracker.getNonceLock(address);
750
+ });
751
+ }
752
+ /**
753
+ * Signs and returns the raw transaction data for provided transaction params list.
754
+ *
755
+ * @param listOfTxParams - The list of transaction params to approve.
756
+ * @returns The raw transactions.
757
+ */
758
+ approveTransactionsWithSameNonce(listOfTxParams = []) {
759
+ return __awaiter(this, void 0, void 0, function* () {
760
+ if (listOfTxParams.length === 0) {
761
+ return '';
762
+ }
763
+ const initialTx = listOfTxParams[0];
764
+ const common = this.getCommonConfiguration();
765
+ const initialTxAsEthTx = tx_1.TransactionFactory.fromTxData(initialTx, {
766
+ common,
767
+ });
768
+ const initialTxAsSerializedHex = (0, ethereumjs_util_1.bufferToHex)(initialTxAsEthTx.serialize());
769
+ if (this.inProcessOfSigning.has(initialTxAsSerializedHex)) {
770
+ return '';
771
+ }
772
+ this.inProcessOfSigning.add(initialTxAsSerializedHex);
773
+ let rawTransactions, nonceLock;
774
+ try {
775
+ // TODO: we should add a check to verify that all transactions have the same from address
776
+ const fromAddress = initialTx.from;
777
+ nonceLock = yield this.nonceTracker.getNonceLock(fromAddress);
778
+ const nonce = nonceLock.nextNonce;
779
+ rawTransactions = yield Promise.all(listOfTxParams.map((txParams) => {
780
+ txParams.nonce = (0, ethereumjs_util_1.addHexPrefix)(nonce.toString(16));
781
+ return this.signExternalTransaction(txParams);
782
+ }));
783
+ }
784
+ catch (err) {
785
+ (0, logger_1.projectLogger)('Error while signing transactions with same nonce', err);
786
+ // Must set transaction to submitted/failed before releasing lock
787
+ // continue with error chain
788
+ throw err;
789
+ }
790
+ finally {
791
+ if (nonceLock) {
792
+ nonceLock.releaseLock();
793
+ }
794
+ this.inProcessOfSigning.delete(initialTxAsSerializedHex);
795
+ }
796
+ return rawTransactions;
797
+ });
798
+ }
799
+ /**
800
+ * Update a custodial transaction.
801
+ *
802
+ * @param transactionId - The ID of the transaction to update.
803
+ * @param options - The custodial transaction options to update.
804
+ * @param options.custodyStatus - The new custody status value to be assigned.
805
+ * @param options.errorMessage - The error message to be assigned in case transaction status update to failed.
806
+ * @param options.hash - The new hash value to be assigned.
807
+ * @param options.status - The new status value to be assigned.
808
+ */
809
+ updateCustodialTransaction(transactionId, { custodyStatus, errorMessage, hash, status, }) {
810
+ let transactionMeta;
811
+ transactionMeta = this.getTransaction(transactionId);
812
+ if (!transactionMeta) {
813
+ throw new Error(`Cannot update custodial transaction as no transaction metadata found`);
814
+ }
815
+ if (!transactionMeta.custodyId) {
816
+ throw new Error('Transaction must be a custodian transaction');
817
+ }
818
+ if (status &&
819
+ ![
820
+ types_1.TransactionStatus.submitted,
821
+ types_1.TransactionStatus.signed,
822
+ types_1.TransactionStatus.failed,
823
+ ].includes(status)) {
824
+ throw new Error(`Cannot update custodial transaction with status: ${status}`);
825
+ }
826
+ if (status === types_1.TransactionStatus.signed) {
827
+ transactionMeta.status = status;
828
+ }
829
+ if (status === types_1.TransactionStatus.submitted) {
830
+ transactionMeta.submittedTime = new Date().getTime();
831
+ transactionMeta.status = status;
832
+ }
833
+ if (status === types_1.TransactionStatus.failed) {
834
+ transactionMeta = Object.assign(Object.assign({}, transactionMeta), { error: (0, utils_1.normalizeTxError)(new Error(errorMessage)), status: types_1.TransactionStatus.failed });
835
+ }
836
+ if (custodyStatus) {
837
+ transactionMeta.custodyStatus = custodyStatus;
838
+ }
839
+ if (hash) {
840
+ transactionMeta.hash = hash;
841
+ }
842
+ this.updateTransaction(transactionMeta, `TransactionController:updateCustodialTransaction - Custodial transaction updated`);
843
+ }
844
+ signExternalTransaction(transactionParams) {
845
+ return __awaiter(this, void 0, void 0, function* () {
846
+ if (!this.sign) {
847
+ throw new Error('No sign method defined.');
848
+ }
849
+ const normalizedTransactionParams = (0, utils_1.normalizeTxParams)(transactionParams);
850
+ const chainId = this.getChainId();
851
+ const type = (0, utils_1.isEIP1559Transaction)(normalizedTransactionParams)
852
+ ? types_1.TransactionEnvelopeType.feeMarket
853
+ : types_1.TransactionEnvelopeType.legacy;
854
+ const updatedTransactionParams = Object.assign(Object.assign({}, normalizedTransactionParams), { type, gasLimit: normalizedTransactionParams.gas, chainId });
855
+ const { from } = updatedTransactionParams;
856
+ const common = this.getCommonConfiguration();
857
+ const unsignedTransaction = tx_1.TransactionFactory.fromTxData(updatedTransactionParams, { common });
858
+ const signedTransaction = yield this.sign(unsignedTransaction, from);
859
+ const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTransaction.serialize());
860
+ return rawTransaction;
861
+ });
862
+ }
863
+ /**
864
+ * Removes unapproved transactions from state.
865
+ */
866
+ clearUnapprovedTransactions() {
867
+ const transactions = this.state.transactions.filter(({ status }) => status !== types_1.TransactionStatus.unapproved);
868
+ this.update({ transactions: this.trimTransactionsForState(transactions) });
869
+ }
870
+ addMetadata(transactionMeta) {
871
+ const { transactions } = this.state;
872
+ transactions.push(transactionMeta);
873
+ this.update({ transactions: this.trimTransactionsForState(transactions) });
874
+ }
875
+ updateGasProperties(transactionMeta) {
876
+ return __awaiter(this, void 0, void 0, function* () {
877
+ const isEIP1559Compatible = yield this.getEIP1559Compatibility();
878
+ const chainId = this.getChainId();
879
+ yield (0, gas_1.updateGas)({
880
+ ethQuery: this.ethQuery,
881
+ providerConfig: this.getNetworkState().providerConfig,
882
+ txMeta: transactionMeta,
883
+ });
884
+ yield (0, gas_fees_1.updateGasFees)({
885
+ eip1559: isEIP1559Compatible,
886
+ ethQuery: this.ethQuery,
887
+ getSavedGasFees: this.getSavedGasFees.bind(this, chainId),
888
+ getGasFeeEstimates: this.getGasFeeEstimates.bind(this),
889
+ txMeta: transactionMeta,
890
+ });
891
+ });
892
+ }
893
+ getCurrentChainTransactionsByStatus(status) {
894
+ const chainId = this.getChainId();
895
+ return this.state.transactions.filter((transaction) => transaction.status === status && transaction.chainId === chainId);
896
+ }
897
+ onBootCleanup() {
898
+ this.createApprovalsForUnapprovedTransactions();
899
+ this.loadGasValuesForUnapprovedTransactions();
900
+ this.submitApprovedTransactions();
901
+ }
902
+ /**
903
+ * Create approvals for all unapproved transactions on current chain.
904
+ */
905
+ createApprovalsForUnapprovedTransactions() {
906
+ const unapprovedTransactions = this.getCurrentChainTransactionsByStatus(types_1.TransactionStatus.unapproved);
907
+ for (const transactionMeta of unapprovedTransactions) {
908
+ this.processApproval(transactionMeta, {
909
+ shouldShowRequest: false,
910
+ }).catch((error) => {
911
+ if ((error === null || error === void 0 ? void 0 : error.code) === rpc_errors_1.errorCodes.provider.userRejectedRequest) {
912
+ return;
913
+ }
914
+ /* istanbul ignore next */
915
+ console.error('Error during persisted transaction approval', error);
916
+ });
917
+ }
918
+ }
919
+ /**
920
+ * Update the gas values of all unapproved transactions on current chain.
921
+ */
922
+ loadGasValuesForUnapprovedTransactions() {
923
+ return __awaiter(this, void 0, void 0, function* () {
924
+ const unapprovedTransactions = this.getCurrentChainTransactionsByStatus(types_1.TransactionStatus.unapproved);
925
+ const results = yield Promise.allSettled(unapprovedTransactions.map((transactionMeta) => __awaiter(this, void 0, void 0, function* () {
926
+ yield this.updateGasProperties(transactionMeta);
927
+ this.updateTransaction(transactionMeta, 'TransactionController:loadGasValuesForUnapprovedTransactions - Gas values updated');
928
+ })));
929
+ for (const [index, result] of results.entries()) {
930
+ if (result.status === 'rejected') {
931
+ const transactionMeta = unapprovedTransactions[index];
932
+ this.failTransaction(transactionMeta, result.reason);
933
+ /* istanbul ignore next */
934
+ console.error('Error while loading gas values for persisted transaction id: ', transactionMeta.id, result.reason);
935
+ }
936
+ }
937
+ });
938
+ }
939
+ /**
940
+ * Force to submit approved transactions on current chain.
941
+ */
942
+ submitApprovedTransactions() {
943
+ const approvedTransactions = this.getCurrentChainTransactionsByStatus(types_1.TransactionStatus.approved);
944
+ for (const transactionMeta of approvedTransactions) {
945
+ if (this.beforeApproveOnInit(transactionMeta)) {
946
+ this.approveTransaction(transactionMeta.id).catch((error) => {
947
+ /* istanbul ignore next */
948
+ console.error('Error while submitting persisted transaction', error);
949
+ });
950
+ }
951
+ }
952
+ }
953
+ processApproval(transactionMeta, { isExisting = false, requireApproval, shouldShowRequest = true, actionId, }) {
666
954
  return __awaiter(this, void 0, void 0, function* () {
667
955
  const transactionId = transactionMeta.id;
668
956
  let resultCallbacks;
@@ -681,17 +969,22 @@ class TransactionController extends base_controller_1.BaseController {
681
969
  const { isCompleted: isTxCompleted } = this.isTransactionCompleted(transactionId);
682
970
  if (!isTxCompleted) {
683
971
  yield this.approveTransaction(transactionId);
972
+ const updatedTransactionMeta = this.getTransaction(transactionId);
973
+ this.hub.emit('transaction-approved', {
974
+ transactionMeta: updatedTransactionMeta,
975
+ actionId,
976
+ });
684
977
  }
685
978
  }
686
979
  catch (error) {
687
980
  const { isCompleted: isTxCompleted } = this.isTransactionCompleted(transactionId);
688
981
  if (!isTxCompleted) {
689
- if (error.code === rpc_errors_1.errorCodes.provider.userRejectedRequest) {
690
- this.cancelTransaction(transactionId);
691
- throw rpc_errors_1.providerErrors.userRejectedRequest('User rejected the transaction');
982
+ if ((error === null || error === void 0 ? void 0 : error.code) === rpc_errors_1.errorCodes.provider.userRejectedRequest) {
983
+ this.cancelTransaction(transactionId, actionId);
984
+ throw rpc_errors_1.providerErrors.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.');
692
985
  }
693
986
  else {
694
- this.failTransaction(meta, error);
987
+ this.failTransaction(meta, error, actionId);
695
988
  }
696
989
  }
697
990
  }
@@ -701,10 +994,6 @@ class TransactionController extends base_controller_1.BaseController {
701
994
  case types_1.TransactionStatus.failed:
702
995
  resultCallbacks === null || resultCallbacks === void 0 ? void 0 : resultCallbacks.error(finalMeta.error);
703
996
  throw rpc_errors_1.rpcErrors.internal(finalMeta.error.message);
704
- case types_1.TransactionStatus.cancelled:
705
- const cancelError = rpc_errors_1.rpcErrors.internal('User cancelled the transaction');
706
- resultCallbacks === null || resultCallbacks === void 0 ? void 0 : resultCallbacks.error(cancelError);
707
- throw cancelError;
708
997
  case types_1.TransactionStatus.submitted:
709
998
  resultCallbacks === null || resultCallbacks === void 0 ? void 0 : resultCallbacks.success();
710
999
  return finalMeta.hash;
@@ -743,6 +1032,10 @@ class TransactionController extends base_controller_1.BaseController {
743
1032
  this.failTransaction(transactionMeta, new Error('No chainId defined.'));
744
1033
  return;
745
1034
  }
1035
+ if (this.inProcessOfSigning.has(transactionId)) {
1036
+ (0, logger_1.projectLogger)('Skipping approval as signing in progress', transactionId);
1037
+ return;
1038
+ }
746
1039
  const { approved: status } = types_1.TransactionStatus;
747
1040
  let nonceToUse = nonce;
748
1041
  // if a nonce already exists on the transactionMeta it means this is a speedup or cancel transaction
@@ -755,27 +1048,31 @@ class TransactionController extends base_controller_1.BaseController {
755
1048
  transactionMeta.txParams.nonce = nonceToUse;
756
1049
  transactionMeta.txParams.chainId = chainId;
757
1050
  const baseTxParams = Object.assign(Object.assign({}, transactionMeta.txParams), { gasLimit: transactionMeta.txParams.gas });
1051
+ this.updateTransaction(transactionMeta, 'TransactionController#approveTransaction - Transaction approved');
758
1052
  const isEIP1559 = (0, utils_1.isEIP1559Transaction)(transactionMeta.txParams);
759
1053
  const txParams = isEIP1559
760
1054
  ? Object.assign(Object.assign({}, baseTxParams), { maxFeePerGas: transactionMeta.txParams.maxFeePerGas, maxPriorityFeePerGas: transactionMeta.txParams.maxPriorityFeePerGas, estimatedBaseFee: transactionMeta.txParams.estimatedBaseFee,
761
1055
  // specify type 2 if maxFeePerGas and maxPriorityFeePerGas are set
762
- type: 2 }) : baseTxParams;
1056
+ type: '2' }) : baseTxParams;
763
1057
  // delete gasPrice if maxFeePerGas and maxPriorityFeePerGas are set
764
1058
  if (isEIP1559) {
765
1059
  delete txParams.gasPrice;
766
1060
  }
767
- const unsignedEthTx = this.prepareUnsignedEthTx(txParams);
768
- const signedTx = yield this.sign(unsignedEthTx, from);
769
- yield this.updateTransactionMetaRSV(transactionMeta, signedTx);
770
- transactionMeta.status = types_1.TransactionStatus.signed;
771
- this.updateTransaction(transactionMeta, 'TransactionController#approveTransaction - Transaction signed');
772
- const rawTx = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
773
- transactionMeta.rawTx = rawTx;
774
- this.updateTransaction(transactionMeta, 'TransactionController#approveTransaction - RawTransaction added');
775
- const hash = yield (0, controller_utils_1.query)(this.ethQuery, 'sendRawTransaction', [rawTx]);
1061
+ const rawTx = yield this.signTransaction(transactionMeta);
1062
+ if (!this.beforePublish(transactionMeta)) {
1063
+ (0, logger_1.projectLogger)('Skipping publishing transaction based on hook');
1064
+ return;
1065
+ }
1066
+ if (!rawTx) {
1067
+ return;
1068
+ }
1069
+ const hash = yield this.publishTransaction(rawTx);
776
1070
  transactionMeta.hash = hash;
777
1071
  transactionMeta.status = types_1.TransactionStatus.submitted;
778
1072
  transactionMeta.submittedTime = new Date().getTime();
1073
+ this.hub.emit('transaction-submitted', {
1074
+ transactionMeta,
1075
+ });
779
1076
  this.updateTransaction(transactionMeta, 'TransactionController#approveTransaction - Transaction submitted');
780
1077
  this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);
781
1078
  }
@@ -783,6 +1080,7 @@ class TransactionController extends base_controller_1.BaseController {
783
1080
  this.failTransaction(transactionMeta, error);
784
1081
  }
785
1082
  finally {
1083
+ this.inProcessOfSigning.delete(transactionId);
786
1084
  // must set transaction to submitted/failed before releasing lock
787
1085
  if (nonceLock) {
788
1086
  nonceLock.releaseLock();
@@ -791,19 +1089,29 @@ class TransactionController extends base_controller_1.BaseController {
791
1089
  }
792
1090
  });
793
1091
  }
1092
+ publishTransaction(rawTransaction) {
1093
+ return __awaiter(this, void 0, void 0, function* () {
1094
+ return yield (0, controller_utils_1.query)(this.ethQuery, 'sendRawTransaction', [rawTransaction]);
1095
+ });
1096
+ }
794
1097
  /**
795
1098
  * Cancels a transaction based on its ID by setting its status to "rejected"
796
1099
  * and emitting a `<tx.id>:finished` hub event.
797
1100
  *
798
1101
  * @param transactionId - The ID of the transaction to cancel.
1102
+ * @param actionId - The actionId passed from UI
799
1103
  */
800
- cancelTransaction(transactionId) {
1104
+ cancelTransaction(transactionId, actionId) {
801
1105
  const transactionMeta = this.state.transactions.find(({ id }) => id === transactionId);
802
1106
  if (!transactionMeta) {
803
1107
  return;
804
1108
  }
805
1109
  transactionMeta.status = types_1.TransactionStatus.rejected;
806
1110
  this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);
1111
+ this.hub.emit('transaction-rejected', {
1112
+ transactionMeta,
1113
+ actionId,
1114
+ });
807
1115
  const transactions = this.state.transactions.filter(({ id }) => id !== transactionId);
808
1116
  this.update({ transactions: this.trimTransactionsForState(transactions) });
809
1117
  }
@@ -852,8 +1160,7 @@ class TransactionController extends base_controller_1.BaseController {
852
1160
  isFinalState(status) {
853
1161
  return (status === types_1.TransactionStatus.rejected ||
854
1162
  status === types_1.TransactionStatus.confirmed ||
855
- status === types_1.TransactionStatus.failed ||
856
- status === types_1.TransactionStatus.cancelled);
1163
+ status === types_1.TransactionStatus.failed);
857
1164
  }
858
1165
  /**
859
1166
  * Whether the transaction has at least completed all local processing.
@@ -863,7 +1170,6 @@ class TransactionController extends base_controller_1.BaseController {
863
1170
  */
864
1171
  isLocalFinalState(status) {
865
1172
  return [
866
- types_1.TransactionStatus.cancelled,
867
1173
  types_1.TransactionStatus.confirmed,
868
1174
  types_1.TransactionStatus.failed,
869
1175
  types_1.TransactionStatus.rejected,
@@ -950,10 +1256,6 @@ class TransactionController extends base_controller_1.BaseController {
950
1256
  this.update({ lastFetchedBlockNumbers });
951
1257
  this.hub.emit('incomingTransactionBlock', blockNumber);
952
1258
  }
953
- onPendingTransactionsUpdate(transactions) {
954
- (0, logger_1.pendingTransactionsLogger)('Updated pending transactions');
955
- this.update({ transactions: this.trimTransactionsForState(transactions) });
956
- }
957
1259
  generateDappSuggestedGasFees(txParams, origin) {
958
1260
  if (!origin || origin === controller_utils_1.ORIGIN_METAMASK) {
959
1261
  return undefined;
@@ -1045,6 +1347,9 @@ class TransactionController extends base_controller_1.BaseController {
1045
1347
  */
1046
1348
  setTransactionStatusDropped(transactionMeta) {
1047
1349
  transactionMeta.status = types_1.TransactionStatus.dropped;
1350
+ this.hub.emit('transaction-dropped', {
1351
+ transactionMeta,
1352
+ });
1048
1353
  this.updateTransaction(transactionMeta, 'TransactionController#setTransactionStatusDropped - Transaction dropped');
1049
1354
  }
1050
1355
  /**
@@ -1093,6 +1398,39 @@ class TransactionController extends base_controller_1.BaseController {
1093
1398
  return (currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible);
1094
1399
  });
1095
1400
  }
1401
+ addPendingTransactionTrackerListeners() {
1402
+ this.pendingTransactionTracker.hub.on('transaction-confirmed', (transactionMeta) => {
1403
+ this.hub.emit('transaction-confirmed', { transactionMeta });
1404
+ this.hub.emit(`${transactionMeta.id}:confirmed`, transactionMeta);
1405
+ });
1406
+ this.pendingTransactionTracker.hub.on('transaction-dropped', this.setTransactionStatusDropped.bind(this));
1407
+ this.pendingTransactionTracker.hub.on('transaction-failed', this.failTransaction.bind(this));
1408
+ this.pendingTransactionTracker.hub.on('transaction-updated', this.updateTransaction.bind(this));
1409
+ }
1410
+ signTransaction(transactionMeta) {
1411
+ var _a;
1412
+ return __awaiter(this, void 0, void 0, function* () {
1413
+ const { txParams } = transactionMeta;
1414
+ const unsignedEthTx = this.prepareUnsignedEthTx(txParams);
1415
+ this.inProcessOfSigning.add(transactionMeta.id);
1416
+ const signedTx = yield ((_a = this.sign) === null || _a === void 0 ? void 0 : _a.call(this, unsignedEthTx, txParams.from, ...this.getAdditionalSignArguments(transactionMeta)));
1417
+ if (!signedTx) {
1418
+ (0, logger_1.projectLogger)('Skipping signed status as no signed transaction');
1419
+ return undefined;
1420
+ }
1421
+ if (!this.afterSign(transactionMeta, signedTx)) {
1422
+ (0, logger_1.projectLogger)('Skipping signed status based on hook');
1423
+ return undefined;
1424
+ }
1425
+ yield this.updateTransactionMetaRSV(transactionMeta, signedTx);
1426
+ transactionMeta.status = types_1.TransactionStatus.signed;
1427
+ this.updateTransaction(transactionMeta, 'TransactionController#approveTransaction - Transaction signed');
1428
+ const rawTx = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
1429
+ transactionMeta.rawTx = rawTx;
1430
+ this.updateTransaction(transactionMeta, 'TransactionController#approveTransaction - RawTransaction added');
1431
+ return rawTx;
1432
+ });
1433
+ }
1096
1434
  }
1097
1435
  exports.TransactionController = TransactionController;
1098
1436
  exports.default = TransactionController;