@metamask/transaction-controller 9.2.0 → 11.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 (42) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/dist/EtherscanRemoteTransactionSource.d.ts +2 -2
  3. package/dist/EtherscanRemoteTransactionSource.d.ts.map +1 -1
  4. package/dist/EtherscanRemoteTransactionSource.js +42 -19
  5. package/dist/EtherscanRemoteTransactionSource.js.map +1 -1
  6. package/dist/IncomingTransactionHelper.d.ts +4 -3
  7. package/dist/IncomingTransactionHelper.d.ts.map +1 -1
  8. package/dist/IncomingTransactionHelper.js +40 -25
  9. package/dist/IncomingTransactionHelper.js.map +1 -1
  10. package/dist/TransactionController.d.ts +114 -26
  11. package/dist/TransactionController.d.ts.map +1 -1
  12. package/dist/TransactionController.js +377 -120
  13. package/dist/TransactionController.js.map +1 -1
  14. package/dist/constants.d.ts +2 -0
  15. package/dist/constants.d.ts.map +1 -1
  16. package/dist/constants.js +26 -26
  17. package/dist/constants.js.map +1 -1
  18. package/dist/etherscan.d.ts +5 -6
  19. package/dist/etherscan.d.ts.map +1 -1
  20. package/dist/etherscan.js +7 -14
  21. package/dist/etherscan.js.map +1 -1
  22. package/dist/external-transactions.d.ts +10 -0
  23. package/dist/external-transactions.d.ts.map +1 -0
  24. package/dist/external-transactions.js +36 -0
  25. package/dist/external-transactions.js.map +1 -0
  26. package/dist/history.d.ts +15 -0
  27. package/dist/history.d.ts.map +1 -0
  28. package/dist/history.js +75 -0
  29. package/dist/history.js.map +1 -0
  30. package/dist/logger.d.ts +6 -0
  31. package/dist/logger.d.ts.map +1 -0
  32. package/dist/logger.js +8 -0
  33. package/dist/logger.js.map +1 -0
  34. package/dist/types.d.ts +129 -20
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/types.js +1 -0
  37. package/dist/types.js.map +1 -1
  38. package/dist/utils.d.ts +19 -10
  39. package/dist/utils.d.ts.map +1 -1
  40. package/dist/utils.js +49 -34
  41. package/dist/utils.js.map +1 -1
  42. package/package.json +5 -3
@@ -23,9 +23,12 @@ const eth_method_registry_1 = __importDefault(require("eth-method-registry"));
23
23
  const eth_rpc_errors_1 = require("eth-rpc-errors");
24
24
  const ethereumjs_util_1 = require("ethereumjs-util");
25
25
  const events_1 = require("events");
26
+ const lodash_1 = require("lodash");
26
27
  const nonce_tracker_1 = __importDefault(require("nonce-tracker"));
27
28
  const uuid_1 = require("uuid");
28
29
  const EtherscanRemoteTransactionSource_1 = require("./EtherscanRemoteTransactionSource");
30
+ const external_transactions_1 = require("./external-transactions");
31
+ const history_1 = require("./history");
29
32
  const IncomingTransactionHelper_1 = require("./IncomingTransactionHelper");
30
33
  const types_1 = require("./types");
31
34
  const utils_1 = require("./utils");
@@ -51,20 +54,22 @@ class TransactionController extends base_controller_1.BaseController {
51
54
  *
52
55
  * @param options - The controller options.
53
56
  * @param options.blockTracker - The block tracker used to poll for new blocks data.
57
+ * @param options.disableHistory - Whether to disable storing history in transaction metadata.
58
+ * @param options.disableSendFlowHistory - Explicitly disable transaction metadata history.
54
59
  * @param options.getNetworkState - Gets the state of the network controller.
55
60
  * @param options.getSelectedAddress - Gets the address of the currently selected account.
56
61
  * @param options.incomingTransactions - Configuration options for incoming transaction support.
57
- * @param options.incomingTransactions.apiKey - An optional API key to use when fetching remote transaction data.
58
62
  * @param options.incomingTransactions.includeTokenTransfers - Whether or not to include ERC20 token transfers.
59
63
  * @param options.incomingTransactions.isEnabled - Whether or not incoming transaction retrieval is enabled.
60
- * @param options.incomingTransactions.updateTransactions - Whether or not to update local transactions using remote transaction data.
64
+ * @param options.incomingTransactions.queryEntireHistory - Whether to initially query the entire transaction history or only recent blocks.
65
+ * @param options.incomingTransactions.updateTransactions - Whether to update local transactions using remote transaction data.
61
66
  * @param options.messenger - The controller messenger.
62
67
  * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.
63
68
  * @param options.provider - The provider used to create the underlying EthQuery instance.
64
69
  * @param config - Initial options used to configure this controller.
65
70
  * @param state - Initial state to set on this controller.
66
71
  */
67
- constructor({ blockTracker, getNetworkState, getSelectedAddress, incomingTransactions = {}, messenger, onNetworkStateChange, provider, }, config, state) {
72
+ constructor({ blockTracker, disableHistory, disableSendFlowHistory, getNetworkState, getSelectedAddress, incomingTransactions = {}, messenger, onNetworkStateChange, provider, }, config, state) {
68
73
  super(config, state);
69
74
  this.mutex = new async_mutex_1.Mutex();
70
75
  /**
@@ -89,6 +94,8 @@ class TransactionController extends base_controller_1.BaseController {
89
94
  this.messagingSystem = messenger;
90
95
  this.getNetworkState = getNetworkState;
91
96
  this.ethQuery = new eth_query_1.default(provider);
97
+ this.isSendFlowHistoryDisabled = disableSendFlowHistory !== null && disableSendFlowHistory !== void 0 ? disableSendFlowHistory : false;
98
+ this.isHistoryDisabled = disableHistory !== null && disableHistory !== void 0 ? disableHistory : false;
92
99
  this.registry = new eth_method_registry_1.default({ provider });
93
100
  this.nonceTracker = new nonce_tracker_1.default({
94
101
  provider,
@@ -99,10 +106,11 @@ class TransactionController extends base_controller_1.BaseController {
99
106
  this.incomingTransactionHelper = new IncomingTransactionHelper_1.IncomingTransactionHelper({
100
107
  blockTracker,
101
108
  getCurrentAccount: getSelectedAddress,
109
+ getLastFetchedBlockNumbers: () => this.state.lastFetchedBlockNumbers,
102
110
  getNetworkState,
103
111
  isEnabled: incomingTransactions.isEnabled,
112
+ queryEntireHistory: incomingTransactions.queryEntireHistory,
104
113
  remoteTransactionSource: new EtherscanRemoteTransactionSource_1.EtherscanRemoteTransactionSource({
105
- apiKey: incomingTransactions.apiKey,
106
114
  includeTokenTransfers: incomingTransactions.includeTokenTransfers,
107
115
  }),
108
116
  transactionLimit: this.config.txHistoryLimit,
@@ -118,7 +126,7 @@ class TransactionController extends base_controller_1.BaseController {
118
126
  }
119
127
  failTransaction(transactionMeta, error) {
120
128
  const newTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { error, status: types_1.TransactionStatus.failed });
121
- this.updateTransaction(newTransactionMeta);
129
+ this.updateTransaction(newTransactionMeta, 'TransactionController#failTransaction - Add error message and set status to failed');
122
130
  this.hub.emit(`${transactionMeta.id}:finished`, newTransactionMeta);
123
131
  }
124
132
  registryLookup(fourBytePrefix) {
@@ -174,48 +182,69 @@ class TransactionController extends base_controller_1.BaseController {
174
182
  * unique transaction id will be generated, and gas and gasPrice will be calculated
175
183
  * if not provided. If A `<tx.id>:unapproved` hub event will be emitted once added.
176
184
  *
177
- * @param transaction - The transaction object to add.
185
+ * @param txParams - Standard parameters for an Ethereum transaction.
178
186
  * @param opts - Additional options to control how the transaction is added.
187
+ * @param opts.actionId - Unique ID to prevent duplicate requests.
179
188
  * @param opts.deviceConfirmedOn - An enum to indicate what device confirmed the transaction.
180
189
  * @param opts.origin - The origin of the transaction request, such as a dApp hostname.
181
190
  * @param opts.requireApproval - Whether the transaction requires approval by the user, defaults to true unless explicitly disabled.
182
191
  * @param opts.securityAlertResponse - Response from security validator.
192
+ * @param opts.sendFlowHistory - The sendFlowHistory entries to add.
183
193
  * @returns Object containing a promise resolving to the transaction hash if approved.
184
194
  */
185
- addTransaction(transaction, { deviceConfirmedOn, origin, requireApproval, securityAlertResponse, } = {}) {
195
+ addTransaction(txParams, { actionId, deviceConfirmedOn, origin, requireApproval, securityAlertResponse, sendFlowHistory, } = {}) {
186
196
  return __awaiter(this, void 0, void 0, function* () {
187
197
  const { chainId, networkId } = this.getChainAndNetworkId();
188
198
  const { transactions } = this.state;
189
- transaction = (0, utils_1.normalizeTransaction)(transaction);
190
- (0, utils_1.validateTransaction)(transaction);
191
- const dappSuggestedGasFees = this.generateDappSuggestedGasFees(transaction, origin);
192
- const transactionMeta = {
199
+ txParams = (0, utils_1.normalizeTxParams)(txParams);
200
+ (0, utils_1.validateTxParams)(txParams);
201
+ const dappSuggestedGasFees = this.generateDappSuggestedGasFees(txParams, origin);
202
+ const existingTransactionMeta = this.getTransactionWithActionId(actionId);
203
+ // If a request to add a transaction with the same actionId is submitted again, a new transaction will not be created for it.
204
+ const transactionMeta = existingTransactionMeta || {
205
+ // Add actionId to txMeta to check if same actionId is seen again
206
+ actionId,
207
+ chainId,
208
+ dappSuggestedGasFees,
209
+ deviceConfirmedOn,
193
210
  id: (0, uuid_1.v1)(),
194
211
  networkID: networkId !== null && networkId !== void 0 ? networkId : undefined,
195
- chainId,
196
212
  origin,
213
+ securityAlertResponse,
197
214
  status: types_1.TransactionStatus.unapproved,
198
215
  time: Date.now(),
199
- transaction,
200
- deviceConfirmedOn,
216
+ txParams,
217
+ userEditedGasLimit: false,
201
218
  verifiedOnBlockchain: false,
202
- dappSuggestedGasFees,
203
- securityAlertResponse,
204
219
  };
205
220
  try {
206
- const { gas, estimateGasError } = yield this.estimateGas(transaction);
207
- transaction.gas = gas;
208
- transaction.estimateGasError = estimateGasError;
221
+ const { gas, estimateGasError } = yield this.estimateGas(txParams);
222
+ txParams.gas = gas;
223
+ txParams.estimateGasError = estimateGasError;
224
+ transactionMeta.originalGasEstimate = gas;
209
225
  }
210
226
  catch (error) {
211
227
  this.failTransaction(transactionMeta, error);
212
228
  return Promise.reject(error);
213
229
  }
214
- transactions.push(transactionMeta);
215
- this.update({ transactions: this.trimTransactionsForState(transactions) });
216
- this.hub.emit(`unapprovedTransaction`, transactionMeta);
230
+ // Checks if a transaction already exists with a given actionId
231
+ if (!existingTransactionMeta) {
232
+ if (!this.isSendFlowHistoryDisabled) {
233
+ transactionMeta.sendFlowHistory = sendFlowHistory !== null && sendFlowHistory !== void 0 ? sendFlowHistory : [];
234
+ }
235
+ // Initial history push
236
+ if (!this.isHistoryDisabled) {
237
+ (0, history_1.addInitialHistorySnapshot)(transactionMeta);
238
+ }
239
+ transactions.push(transactionMeta);
240
+ this.update({
241
+ transactions: this.trimTransactionsForState(transactions),
242
+ });
243
+ this.hub.emit(`unapprovedTransaction`, transactionMeta);
244
+ }
217
245
  return {
218
246
  result: this.processApproval(transactionMeta, {
247
+ isExisting: Boolean(existingTransactionMeta),
219
248
  requireApproval,
220
249
  }),
221
250
  transactionMeta,
@@ -253,18 +282,18 @@ class TransactionController extends base_controller_1.BaseController {
253
282
  * Attempts to cancel a transaction based on its ID by setting its status to "rejected"
254
283
  * and emitting a `<tx.id>:finished` hub event.
255
284
  *
256
- * @param transactionID - The ID of the transaction to cancel.
285
+ * @param transactionId - The ID of the transaction to cancel.
257
286
  * @param gasValues - The gas values to use for the cancellation transaction.
258
287
  * @param options - The options for the cancellation transaction.
259
288
  * @param options.estimatedBaseFee - The estimated base fee of the transaction.
260
289
  */
261
- stopTransaction(transactionID, gasValues, { estimatedBaseFee } = {}) {
290
+ stopTransaction(transactionId, gasValues, { estimatedBaseFee } = {}) {
262
291
  var _a, _b;
263
292
  return __awaiter(this, void 0, void 0, function* () {
264
293
  if (gasValues) {
265
294
  (0, utils_1.validateGasValues)(gasValues);
266
295
  }
267
- const transactionMeta = this.state.transactions.find(({ id }) => id === transactionID);
296
+ const transactionMeta = this.state.transactions.find(({ id }) => id === transactionId);
268
297
  if (!transactionMeta) {
269
298
  return;
270
299
  }
@@ -272,20 +301,20 @@ class TransactionController extends base_controller_1.BaseController {
272
301
  throw new Error('No sign method defined.');
273
302
  }
274
303
  // gasPrice (legacy non EIP1559)
275
- const minGasPrice = (0, utils_1.getIncreasedPriceFromExisting)(transactionMeta.transaction.gasPrice, exports.CANCEL_RATE);
304
+ const minGasPrice = (0, utils_1.getIncreasedPriceFromExisting)(transactionMeta.txParams.gasPrice, exports.CANCEL_RATE);
276
305
  const gasPriceFromValues = (0, utils_1.isGasPriceValue)(gasValues) && gasValues.gasPrice;
277
306
  const newGasPrice = (gasPriceFromValues &&
278
307
  (0, utils_1.validateMinimumIncrease)(gasPriceFromValues, minGasPrice)) ||
279
308
  minGasPrice;
280
309
  // maxFeePerGas (EIP1559)
281
- const existingMaxFeePerGas = (_a = transactionMeta.transaction) === null || _a === void 0 ? void 0 : _a.maxFeePerGas;
310
+ const existingMaxFeePerGas = (_a = transactionMeta.txParams) === null || _a === void 0 ? void 0 : _a.maxFeePerGas;
282
311
  const minMaxFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxFeePerGas, exports.CANCEL_RATE);
283
312
  const maxFeePerGasValues = (0, utils_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxFeePerGas;
284
313
  const newMaxFeePerGas = (maxFeePerGasValues &&
285
314
  (0, utils_1.validateMinimumIncrease)(maxFeePerGasValues, minMaxFeePerGas)) ||
286
315
  (existingMaxFeePerGas && minMaxFeePerGas);
287
316
  // maxPriorityFeePerGas (EIP1559)
288
- const existingMaxPriorityFeePerGas = (_b = transactionMeta.transaction) === null || _b === void 0 ? void 0 : _b.maxPriorityFeePerGas;
317
+ const existingMaxPriorityFeePerGas = (_b = transactionMeta.txParams) === null || _b === void 0 ? void 0 : _b.maxPriorityFeePerGas;
289
318
  const minMaxPriorityFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxPriorityFeePerGas, exports.CANCEL_RATE);
290
319
  const maxPriorityFeePerGasValues = (0, utils_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxPriorityFeePerGas;
291
320
  const newMaxPriorityFeePerGas = (maxPriorityFeePerGasValues &&
@@ -293,27 +322,28 @@ class TransactionController extends base_controller_1.BaseController {
293
322
  (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);
294
323
  const txParams = newMaxFeePerGas && newMaxPriorityFeePerGas
295
324
  ? {
296
- from: transactionMeta.transaction.from,
297
- gasLimit: transactionMeta.transaction.gas,
325
+ from: transactionMeta.txParams.from,
326
+ gasLimit: transactionMeta.txParams.gas,
298
327
  maxFeePerGas: newMaxFeePerGas,
299
328
  maxPriorityFeePerGas: newMaxPriorityFeePerGas,
300
329
  type: 2,
301
- nonce: transactionMeta.transaction.nonce,
302
- to: transactionMeta.transaction.from,
330
+ nonce: transactionMeta.txParams.nonce,
331
+ to: transactionMeta.txParams.from,
303
332
  value: '0x0',
304
333
  }
305
334
  : {
306
- from: transactionMeta.transaction.from,
307
- gasLimit: transactionMeta.transaction.gas,
335
+ from: transactionMeta.txParams.from,
336
+ gasLimit: transactionMeta.txParams.gas,
308
337
  gasPrice: newGasPrice,
309
- nonce: transactionMeta.transaction.nonce,
310
- to: transactionMeta.transaction.from,
338
+ nonce: transactionMeta.txParams.nonce,
339
+ to: transactionMeta.txParams.from,
311
340
  value: '0x0',
312
341
  };
313
342
  const unsignedEthTx = this.prepareUnsignedEthTx(txParams);
314
- const signedTx = yield this.sign(unsignedEthTx, transactionMeta.transaction.from);
315
- const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
316
- yield (0, controller_utils_1.query)(this.ethQuery, 'sendRawTransaction', [rawTransaction]);
343
+ const signedTx = yield this.sign(unsignedEthTx, transactionMeta.txParams.from);
344
+ yield this.updateTransactionMetaRSV(transactionMeta, signedTx);
345
+ const rawTx = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
346
+ yield (0, controller_utils_1.query)(this.ethQuery, 'sendRawTransaction', [rawTx]);
317
347
  transactionMeta.estimatedBaseFee = estimatedBaseFee;
318
348
  transactionMeta.status = types_1.TransactionStatus.cancelled;
319
349
  this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);
@@ -322,18 +352,23 @@ class TransactionController extends base_controller_1.BaseController {
322
352
  /**
323
353
  * Attempts to speed up a transaction increasing transaction gasPrice by ten percent.
324
354
  *
325
- * @param transactionID - The ID of the transaction to speed up.
326
- * @param gasValues - The gas values to use for the speed up transation.
355
+ * @param transactionId - The ID of the transaction to speed up.
356
+ * @param gasValues - The gas values to use for the speed up transaction.
327
357
  * @param options - The options for the speed up transaction.
358
+ * @param options.actionId - Unique ID to prevent duplicate requests
328
359
  * @param options.estimatedBaseFee - The estimated base fee of the transaction.
329
360
  */
330
- speedUpTransaction(transactionID, gasValues, { estimatedBaseFee } = {}) {
361
+ speedUpTransaction(transactionId, gasValues, { actionId, estimatedBaseFee, } = {}) {
331
362
  var _a, _b;
332
363
  return __awaiter(this, void 0, void 0, function* () {
364
+ // If transaction is found for same action id, do not create a new speed up transaction.
365
+ if (this.getTransactionWithActionId(actionId)) {
366
+ return;
367
+ }
333
368
  if (gasValues) {
334
369
  (0, utils_1.validateGasValues)(gasValues);
335
370
  }
336
- const transactionMeta = this.state.transactions.find(({ id }) => id === transactionID);
371
+ const transactionMeta = this.state.transactions.find(({ id }) => id === transactionId);
337
372
  /* istanbul ignore next */
338
373
  if (!transactionMeta) {
339
374
  return;
@@ -344,36 +379,36 @@ class TransactionController extends base_controller_1.BaseController {
344
379
  }
345
380
  const { transactions } = this.state;
346
381
  // gasPrice (legacy non EIP1559)
347
- const minGasPrice = (0, utils_1.getIncreasedPriceFromExisting)(transactionMeta.transaction.gasPrice, exports.SPEED_UP_RATE);
382
+ const minGasPrice = (0, utils_1.getIncreasedPriceFromExisting)(transactionMeta.txParams.gasPrice, exports.SPEED_UP_RATE);
348
383
  const gasPriceFromValues = (0, utils_1.isGasPriceValue)(gasValues) && gasValues.gasPrice;
349
384
  const newGasPrice = (gasPriceFromValues &&
350
385
  (0, utils_1.validateMinimumIncrease)(gasPriceFromValues, minGasPrice)) ||
351
386
  minGasPrice;
352
387
  // maxFeePerGas (EIP1559)
353
- const existingMaxFeePerGas = (_a = transactionMeta.transaction) === null || _a === void 0 ? void 0 : _a.maxFeePerGas;
388
+ const existingMaxFeePerGas = (_a = transactionMeta.txParams) === null || _a === void 0 ? void 0 : _a.maxFeePerGas;
354
389
  const minMaxFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxFeePerGas, exports.SPEED_UP_RATE);
355
390
  const maxFeePerGasValues = (0, utils_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxFeePerGas;
356
391
  const newMaxFeePerGas = (maxFeePerGasValues &&
357
392
  (0, utils_1.validateMinimumIncrease)(maxFeePerGasValues, minMaxFeePerGas)) ||
358
393
  (existingMaxFeePerGas && minMaxFeePerGas);
359
394
  // maxPriorityFeePerGas (EIP1559)
360
- const existingMaxPriorityFeePerGas = (_b = transactionMeta.transaction) === null || _b === void 0 ? void 0 : _b.maxPriorityFeePerGas;
395
+ const existingMaxPriorityFeePerGas = (_b = transactionMeta.txParams) === null || _b === void 0 ? void 0 : _b.maxPriorityFeePerGas;
361
396
  const minMaxPriorityFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxPriorityFeePerGas, exports.SPEED_UP_RATE);
362
397
  const maxPriorityFeePerGasValues = (0, utils_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxPriorityFeePerGas;
363
398
  const newMaxPriorityFeePerGas = (maxPriorityFeePerGasValues &&
364
399
  (0, utils_1.validateMinimumIncrease)(maxPriorityFeePerGasValues, minMaxPriorityFeePerGas)) ||
365
400
  (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);
366
401
  const txParams = newMaxFeePerGas && newMaxPriorityFeePerGas
367
- ? Object.assign(Object.assign({}, transactionMeta.transaction), { gasLimit: transactionMeta.transaction.gas, maxFeePerGas: newMaxFeePerGas, maxPriorityFeePerGas: newMaxPriorityFeePerGas, type: 2 }) : Object.assign(Object.assign({}, transactionMeta.transaction), { gasLimit: transactionMeta.transaction.gas, gasPrice: newGasPrice });
402
+ ? 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 });
368
403
  const unsignedEthTx = this.prepareUnsignedEthTx(txParams);
369
- const signedTx = yield this.sign(unsignedEthTx, transactionMeta.transaction.from);
370
- const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
371
- const transactionHash = yield (0, controller_utils_1.query)(this.ethQuery, 'sendRawTransaction', [
372
- rawTransaction,
373
- ]);
374
- const baseTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { estimatedBaseFee, id: (0, uuid_1.v1)(), time: Date.now(), transactionHash });
404
+ const signedTx = yield this.sign(unsignedEthTx, transactionMeta.txParams.from);
405
+ yield this.updateTransactionMetaRSV(transactionMeta, signedTx);
406
+ const rawTx = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
407
+ const hash = yield (0, controller_utils_1.query)(this.ethQuery, 'sendRawTransaction', [rawTx]);
408
+ const baseTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { estimatedBaseFee, id: (0, uuid_1.v1)(), time: Date.now(), hash,
409
+ actionId, originalGasEstimate: transactionMeta.txParams.gas });
375
410
  const newTransactionMeta = newMaxFeePerGas && newMaxPriorityFeePerGas
376
- ? Object.assign(Object.assign({}, baseTransactionMeta), { transaction: Object.assign(Object.assign({}, transactionMeta.transaction), { maxFeePerGas: newMaxFeePerGas, maxPriorityFeePerGas: newMaxPriorityFeePerGas }) }) : Object.assign(Object.assign({}, baseTransactionMeta), { transaction: Object.assign(Object.assign({}, transactionMeta.transaction), { gasPrice: newGasPrice }) });
411
+ ? 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 }) });
377
412
  transactions.push(newTransactionMeta);
378
413
  this.update({ transactions: this.trimTransactionsForState(transactions) });
379
414
  this.hub.emit(`${transactionMeta.id}:speedup`, newTransactionMeta);
@@ -403,7 +438,7 @@ class TransactionController extends base_controller_1.BaseController {
403
438
  false,
404
439
  ]);
405
440
  // 2. If to is not defined or this is not a contract address, and there is no data use 0x5208 / 21000.
406
- // If the newtwork is a custom network then bypass this check and fetch 'estimateGas'.
441
+ // If the network is a custom network then bypass this check and fetch 'estimateGas'.
407
442
  /* istanbul ignore next */
408
443
  const code = to ? yield (0, controller_utils_1.query)(this.ethQuery, 'getCode', [to]) : undefined;
409
444
  /* istanbul ignore next */
@@ -484,11 +519,15 @@ class TransactionController extends base_controller_1.BaseController {
484
519
  * Updates an existing transaction in state.
485
520
  *
486
521
  * @param transactionMeta - The new transaction to store in state.
522
+ * @param note - A note or update reason to include in the transaction history.
487
523
  */
488
- updateTransaction(transactionMeta) {
524
+ updateTransaction(transactionMeta, note) {
489
525
  const { transactions } = this.state;
490
- transactionMeta.transaction = (0, utils_1.normalizeTransaction)(transactionMeta.transaction);
491
- (0, utils_1.validateTransaction)(transactionMeta.transaction);
526
+ transactionMeta.txParams = (0, utils_1.normalizeTxParams)(transactionMeta.txParams);
527
+ (0, utils_1.validateTxParams)(transactionMeta.txParams);
528
+ if (!this.isHistoryDisabled) {
529
+ (0, history_1.updateTransactionHistory)(transactionMeta, note);
530
+ }
492
531
  const index = transactions.findIndex(({ id }) => transactionMeta.id === id);
493
532
  transactions[index] = transactionMeta;
494
533
  this.update({ transactions: this.trimTransactionsForState(transactions) });
@@ -508,7 +547,7 @@ class TransactionController extends base_controller_1.BaseController {
508
547
  return;
509
548
  }
510
549
  const { chainId: currentChainId, networkId: currentNetworkID } = this.getChainAndNetworkId();
511
- const newTransactions = this.state.transactions.filter(({ networkID, chainId, transaction }) => {
550
+ const newTransactions = this.state.transactions.filter(({ networkID, chainId, txParams }) => {
512
551
  var _a;
513
552
  // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed.
514
553
  const isMatchingNetwork = ignoreNetwork ||
@@ -517,7 +556,7 @@ class TransactionController extends base_controller_1.BaseController {
517
556
  if (!isMatchingNetwork) {
518
557
  return true;
519
558
  }
520
- const isMatchingAddress = !address || ((_a = transaction.from) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === address.toLowerCase();
559
+ const isMatchingAddress = !address || ((_a = txParams.from) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === address.toLowerCase();
521
560
  return !isMatchingAddress;
522
561
  });
523
562
  this.update({
@@ -530,35 +569,145 @@ class TransactionController extends base_controller_1.BaseController {
530
569
  stopIncomingTransactionProcessing() {
531
570
  this.incomingTransactionHelper.stop();
532
571
  }
533
- processApproval(transactionMeta, { requireApproval, shouldShowRequest = true, }) {
572
+ /**
573
+ * Adds external provided transaction to state as confirmed transaction.
574
+ *
575
+ * @param transactionMeta - TransactionMeta to add transactions.
576
+ * @param transactionReceipt - TransactionReceipt of the external transaction.
577
+ * @param baseFeePerGas - Base fee per gas of the external transaction.
578
+ */
579
+ confirmExternalTransaction(transactionMeta, transactionReceipt, baseFeePerGas) {
534
580
  return __awaiter(this, void 0, void 0, function* () {
535
- const transactionId = transactionMeta.id;
536
- let resultCallbacks;
581
+ // Run validation and add external transaction to state.
582
+ this.addExternalTransaction(transactionMeta);
537
583
  try {
538
- if (requireApproval !== false) {
539
- const acceptResult = yield this.requestApproval(transactionMeta, {
540
- shouldShowRequest,
541
- });
542
- resultCallbacks = acceptResult.resultCallbacks;
543
- }
544
- const { meta, isCompleted } = this.isTransactionCompleted(transactionId);
545
- if (meta && !isCompleted) {
546
- yield this.approveTransaction(transactionId);
584
+ const transactionId = transactionMeta.id;
585
+ // Make sure status is confirmed and define gasUsed as in receipt.
586
+ transactionMeta.status = types_1.TransactionStatus.confirmed;
587
+ transactionMeta.txReceipt = transactionReceipt;
588
+ if (baseFeePerGas) {
589
+ transactionMeta.baseFeePerGas = baseFeePerGas;
547
590
  }
591
+ // Update same nonce local transactions as dropped and define replacedBy properties.
592
+ this.markNonceDuplicatesDropped(transactionId);
593
+ // Update external provided transaction with updated gas values and confirmed status.
594
+ this.updateTransaction(transactionMeta, 'TransactionController:confirmExternalTransaction - Add external transaction');
548
595
  }
549
596
  catch (error) {
550
- const { meta, isCompleted } = this.isTransactionCompleted(transactionId);
551
- if (meta && !isCompleted) {
552
- if (error.code === eth_rpc_errors_1.errorCodes.provider.userRejectedRequest) {
553
- this.cancelTransaction(transactionId);
554
- throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the transaction');
597
+ console.error(error);
598
+ }
599
+ });
600
+ }
601
+ /**
602
+ * Append new send flow history to a transaction.
603
+ *
604
+ * @param transactionID - The ID of the transaction to update.
605
+ * @param currentSendFlowHistoryLength - The length of the current sendFlowHistory array.
606
+ * @param sendFlowHistoryToAdd - The sendFlowHistory entries to add.
607
+ * @returns The updated transactionMeta.
608
+ */
609
+ updateTransactionSendFlowHistory(transactionID, currentSendFlowHistoryLength, sendFlowHistoryToAdd) {
610
+ var _a, _b;
611
+ if (this.isSendFlowHistoryDisabled) {
612
+ throw new Error('Send flow history is disabled for the current transaction controller');
613
+ }
614
+ const transactionMeta = this.getTransaction(transactionID);
615
+ if (!transactionMeta) {
616
+ throw new Error(`Cannot update send flow history as no transaction metadata found`);
617
+ }
618
+ (0, utils_1.validateIfTransactionUnapproved)(transactionMeta, 'updateTransactionSendFlowHistory');
619
+ if (currentSendFlowHistoryLength ===
620
+ (((_a = transactionMeta === null || transactionMeta === void 0 ? void 0 : transactionMeta.sendFlowHistory) === null || _a === void 0 ? void 0 : _a.length) || 0)) {
621
+ transactionMeta.sendFlowHistory = [
622
+ ...((_b = transactionMeta === null || transactionMeta === void 0 ? void 0 : transactionMeta.sendFlowHistory) !== null && _b !== void 0 ? _b : []),
623
+ ...sendFlowHistoryToAdd,
624
+ ];
625
+ this.updateTransaction(transactionMeta, 'TransactionController:updateTransactionSendFlowHistory - sendFlowHistory updated');
626
+ }
627
+ return this.getTransaction(transactionID);
628
+ }
629
+ /**
630
+ * Update the gas values of a transaction.
631
+ *
632
+ * @param transactionId - The ID of the transaction to update.
633
+ * @param gasValues - Gas values to update.
634
+ * @param gasValues.gas - Same as transaction.gasLimit.
635
+ * @param gasValues.gasLimit - Maxmimum number of units of gas to use for this transaction.
636
+ * @param gasValues.gasPrice - Price per gas for legacy transactions.
637
+ * @param gasValues.maxPriorityFeePerGas - Maximum amount per gas to give to validator as incentive.
638
+ * @param gasValues.maxFeePerGas - Maximum amount per gas to pay for the transaction, including the priority fee.
639
+ * @param gasValues.estimateUsed - Which estimate level was used.
640
+ * @param gasValues.estimateSuggested - Which estimate level that the API suggested.
641
+ * @param gasValues.defaultGasEstimates - The default estimate for gas.
642
+ * @param gasValues.originalGasEstimate - Original estimate for gas.
643
+ * @param gasValues.userEditedGasLimit - The gas limit supplied by user.
644
+ * @param gasValues.userFeeLevel - Estimate level user selected.
645
+ * @returns The updated transactionMeta.
646
+ */
647
+ updateTransactionGasFees(transactionId, { defaultGasEstimates, estimateUsed, estimateSuggested, gas, gasLimit, gasPrice, maxPriorityFeePerGas, maxFeePerGas, originalGasEstimate, userEditedGasLimit, userFeeLevel, }) {
648
+ const transactionMeta = this.getTransaction(transactionId);
649
+ if (!transactionMeta) {
650
+ throw new Error(`Cannot update transaction as no transaction metadata found`);
651
+ }
652
+ (0, utils_1.validateIfTransactionUnapproved)(transactionMeta, 'updateTransactionGasFees');
653
+ let transactionGasFees = {
654
+ txParams: {
655
+ gas,
656
+ gasLimit,
657
+ gasPrice,
658
+ maxPriorityFeePerGas,
659
+ maxFeePerGas,
660
+ },
661
+ defaultGasEstimates,
662
+ estimateUsed,
663
+ estimateSuggested,
664
+ originalGasEstimate,
665
+ userEditedGasLimit,
666
+ userFeeLevel,
667
+ };
668
+ // only update what is defined
669
+ transactionGasFees.txParams = (0, lodash_1.pickBy)(transactionGasFees.txParams);
670
+ transactionGasFees = (0, lodash_1.pickBy)(transactionGasFees);
671
+ // merge updated gas values with existing transaction meta
672
+ const updatedMeta = (0, lodash_1.merge)(transactionMeta, transactionGasFees);
673
+ this.updateTransaction(updatedMeta, 'TransactionController:updateTransactionGasFees - gas values updated');
674
+ return this.getTransaction(transactionId);
675
+ }
676
+ processApproval(transactionMeta, { isExisting = false, requireApproval, shouldShowRequest = true, }) {
677
+ return __awaiter(this, void 0, void 0, function* () {
678
+ const transactionId = transactionMeta.id;
679
+ let resultCallbacks;
680
+ const { meta, isCompleted } = this.isTransactionCompleted(transactionId);
681
+ const finishedPromise = isCompleted
682
+ ? Promise.resolve(meta)
683
+ : this.waitForTransactionFinished(transactionId);
684
+ if (meta && !isExisting && !isCompleted) {
685
+ try {
686
+ if (requireApproval !== false) {
687
+ const acceptResult = yield this.requestApproval(transactionMeta, {
688
+ shouldShowRequest,
689
+ });
690
+ resultCallbacks = acceptResult.resultCallbacks;
555
691
  }
556
- else {
557
- this.failTransaction(meta, error);
692
+ const { isCompleted: isTxCompleted } = this.isTransactionCompleted(transactionId);
693
+ if (!isTxCompleted) {
694
+ yield this.approveTransaction(transactionId);
695
+ }
696
+ }
697
+ catch (error) {
698
+ const { isCompleted: isTxCompleted } = this.isTransactionCompleted(transactionId);
699
+ if (!isTxCompleted) {
700
+ if (error.code === eth_rpc_errors_1.errorCodes.provider.userRejectedRequest) {
701
+ this.cancelTransaction(transactionId);
702
+ throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the transaction');
703
+ }
704
+ else {
705
+ this.failTransaction(meta, error);
706
+ }
558
707
  }
559
708
  }
560
709
  }
561
- const finalMeta = this.getTransaction(transactionId);
710
+ const finalMeta = yield finishedPromise;
562
711
  switch (finalMeta === null || finalMeta === void 0 ? void 0 : finalMeta.status) {
563
712
  case types_1.TransactionStatus.failed:
564
713
  resultCallbacks === null || resultCallbacks === void 0 ? void 0 : resultCallbacks.error(finalMeta.error);
@@ -569,7 +718,7 @@ class TransactionController extends base_controller_1.BaseController {
569
718
  throw cancelError;
570
719
  case types_1.TransactionStatus.submitted:
571
720
  resultCallbacks === null || resultCallbacks === void 0 ? void 0 : resultCallbacks.success();
572
- return finalMeta.transactionHash;
721
+ return finalMeta.hash;
573
722
  default:
574
723
  const internalError = eth_rpc_errors_1.ethErrors.rpc.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finalMeta || transactionId)}`);
575
724
  resultCallbacks === null || resultCallbacks === void 0 ? void 0 : resultCallbacks.error(internalError);
@@ -583,16 +732,16 @@ class TransactionController extends base_controller_1.BaseController {
583
732
  * using the sign configuration property, then published to the blockchain.
584
733
  * A `<tx.id>:finished` hub event is fired after success or failure.
585
734
  *
586
- * @param transactionID - The ID of the transaction to approve.
735
+ * @param transactionId - The ID of the transaction to approve.
587
736
  */
588
- approveTransaction(transactionID) {
737
+ approveTransaction(transactionId) {
589
738
  return __awaiter(this, void 0, void 0, function* () {
590
739
  const { transactions } = this.state;
591
740
  const releaseLock = yield this.mutex.acquire();
592
741
  const { chainId } = this.getChainAndNetworkId();
593
- const index = transactions.findIndex(({ id }) => transactionID === id);
742
+ const index = transactions.findIndex(({ id }) => transactionId === id);
594
743
  const transactionMeta = transactions[index];
595
- const { transaction: { nonce, from }, } = transactionMeta;
744
+ const { txParams: { nonce, from }, } = transactionMeta;
596
745
  let nonceLock;
597
746
  try {
598
747
  if (!this.sign) {
@@ -614,12 +763,12 @@ class TransactionController extends base_controller_1.BaseController {
614
763
  nonceToUse = (0, ethereumjs_util_1.addHexPrefix)(nonceLock.nextNonce.toString(16));
615
764
  }
616
765
  transactionMeta.status = status;
617
- transactionMeta.transaction.nonce = nonceToUse;
618
- transactionMeta.transaction.chainId = chainId;
619
- const baseTxParams = Object.assign(Object.assign({}, transactionMeta.transaction), { gasLimit: transactionMeta.transaction.gas });
620
- const isEIP1559 = (0, utils_1.isEIP1559Transaction)(transactionMeta.transaction);
766
+ transactionMeta.txParams.nonce = nonceToUse;
767
+ transactionMeta.txParams.chainId = chainId;
768
+ const baseTxParams = Object.assign(Object.assign({}, transactionMeta.txParams), { gasLimit: transactionMeta.txParams.gas });
769
+ const isEIP1559 = (0, utils_1.isEIP1559Transaction)(transactionMeta.txParams);
621
770
  const txParams = isEIP1559
622
- ? Object.assign(Object.assign({}, baseTxParams), { maxFeePerGas: transactionMeta.transaction.maxFeePerGas, maxPriorityFeePerGas: transactionMeta.transaction.maxPriorityFeePerGas, estimatedBaseFee: transactionMeta.transaction.estimatedBaseFee,
771
+ ? Object.assign(Object.assign({}, baseTxParams), { maxFeePerGas: transactionMeta.txParams.maxFeePerGas, maxPriorityFeePerGas: transactionMeta.txParams.maxPriorityFeePerGas, estimatedBaseFee: transactionMeta.txParams.estimatedBaseFee,
623
772
  // specify type 2 if maxFeePerGas and maxPriorityFeePerGas are set
624
773
  type: 2 }) : baseTxParams;
625
774
  // delete gasPrice if maxFeePerGas and maxPriorityFeePerGas are set
@@ -628,17 +777,17 @@ class TransactionController extends base_controller_1.BaseController {
628
777
  }
629
778
  const unsignedEthTx = this.prepareUnsignedEthTx(txParams);
630
779
  const signedTx = yield this.sign(unsignedEthTx, from);
780
+ yield this.updateTransactionMetaRSV(transactionMeta, signedTx);
631
781
  transactionMeta.status = types_1.TransactionStatus.signed;
632
- this.updateTransaction(transactionMeta);
633
- const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
634
- transactionMeta.rawTransaction = rawTransaction;
635
- this.updateTransaction(transactionMeta);
636
- const transactionHash = yield (0, controller_utils_1.query)(this.ethQuery, 'sendRawTransaction', [
637
- rawTransaction,
638
- ]);
639
- transactionMeta.transactionHash = transactionHash;
782
+ this.updateTransaction(transactionMeta, 'TransactionController#approveTransaction - Transaction signed');
783
+ const rawTx = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
784
+ transactionMeta.rawTx = rawTx;
785
+ this.updateTransaction(transactionMeta, 'TransactionController#approveTransaction - RawTransaction added');
786
+ const hash = yield (0, controller_utils_1.query)(this.ethQuery, 'sendRawTransaction', [rawTx]);
787
+ transactionMeta.hash = hash;
640
788
  transactionMeta.status = types_1.TransactionStatus.submitted;
641
- this.updateTransaction(transactionMeta);
789
+ transactionMeta.submittedTime = new Date().getTime();
790
+ this.updateTransaction(transactionMeta, 'TransactionController#approveTransaction - Transaction submitted');
642
791
  this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);
643
792
  }
644
793
  catch (error) {
@@ -657,16 +806,16 @@ class TransactionController extends base_controller_1.BaseController {
657
806
  * Cancels a transaction based on its ID by setting its status to "rejected"
658
807
  * and emitting a `<tx.id>:finished` hub event.
659
808
  *
660
- * @param transactionID - The ID of the transaction to cancel.
809
+ * @param transactionId - The ID of the transaction to cancel.
661
810
  */
662
- cancelTransaction(transactionID) {
663
- const transactionMeta = this.state.transactions.find(({ id }) => id === transactionID);
811
+ cancelTransaction(transactionId) {
812
+ const transactionMeta = this.state.transactions.find(({ id }) => id === transactionId);
664
813
  if (!transactionMeta) {
665
814
  return;
666
815
  }
667
816
  transactionMeta.status = types_1.TransactionStatus.rejected;
668
817
  this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);
669
- const transactions = this.state.transactions.filter(({ id }) => id !== transactionID);
818
+ const transactions = this.state.transactions.filter(({ id }) => id !== transactionId);
670
819
  this.update({ transactions: this.trimTransactionsForState(transactions) });
671
820
  }
672
821
  /**
@@ -685,10 +834,12 @@ class TransactionController extends base_controller_1.BaseController {
685
834
  */
686
835
  trimTransactionsForState(transactions) {
687
836
  const nonceNetworkSet = new Set();
688
- const txsToKeep = transactions.reverse().filter((tx) => {
689
- const { chainId, networkID, status, transaction, time } = tx;
690
- if (transaction) {
691
- const key = `${transaction.nonce}-${chainId ? (0, controller_utils_1.convertHexToDecimal)(chainId) : networkID}-${new Date(time).toDateString()}`;
837
+ const txsToKeep = transactions
838
+ .sort((a, b) => (a.time > b.time ? -1 : 1)) // Descending time order
839
+ .filter((tx) => {
840
+ const { chainId, networkID, status, txParams, time } = tx;
841
+ if (txParams) {
842
+ const key = `${txParams.nonce}-${chainId ? (0, controller_utils_1.convertHexToDecimal)(chainId) : networkID}-${new Date(time).toDateString()}`;
692
843
  if (nonceNetworkSet.has(key)) {
693
844
  return true;
694
845
  }
@@ -700,7 +851,7 @@ class TransactionController extends base_controller_1.BaseController {
700
851
  }
701
852
  return false;
702
853
  });
703
- txsToKeep.reverse();
854
+ txsToKeep.reverse(); // Ascending time order
704
855
  return txsToKeep;
705
856
  }
706
857
  /**
@@ -738,11 +889,11 @@ class TransactionController extends base_controller_1.BaseController {
738
889
  */
739
890
  blockchainTransactionStateReconciler(meta) {
740
891
  return __awaiter(this, void 0, void 0, function* () {
741
- const { status, transactionHash } = meta;
892
+ const { status, hash } = meta;
742
893
  switch (status) {
743
894
  case types_1.TransactionStatus.confirmed:
744
895
  const txReceipt = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionReceipt', [
745
- transactionHash,
896
+ hash,
746
897
  ]);
747
898
  if (!txReceipt) {
748
899
  return [meta, false];
@@ -751,7 +902,7 @@ class TransactionController extends base_controller_1.BaseController {
751
902
  txReceipt.blockHash,
752
903
  ]);
753
904
  meta.verifiedOnBlockchain = true;
754
- meta.transaction.gasUsed = txReceipt.gasUsed;
905
+ meta.txParams.gasUsed = txReceipt.gasUsed;
755
906
  meta.txReceipt = txReceipt;
756
907
  meta.baseFeePerGas = txBlock === null || txBlock === void 0 ? void 0 : txBlock.baseFeePerGas;
757
908
  meta.blockTimestamp = txBlock === null || txBlock === void 0 ? void 0 : txBlock.timestamp;
@@ -765,10 +916,10 @@ class TransactionController extends base_controller_1.BaseController {
765
916
  return [meta, true];
766
917
  case types_1.TransactionStatus.submitted:
767
918
  const txObj = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionByHash', [
768
- transactionHash,
919
+ hash,
769
920
  ]);
770
921
  if (!txObj) {
771
- const receiptShowsFailedStatus = yield this.checkTxReceiptStatusIsFailed(transactionHash);
922
+ const receiptShowsFailedStatus = yield this.checkTxReceiptStatusIsFailed(hash);
772
923
  // Case the txObj is evaluated as false, a second check will
773
924
  // determine if the tx failed or it is pending or confirmed
774
925
  if (receiptShowsFailedStatus) {
@@ -824,15 +975,15 @@ class TransactionController extends base_controller_1.BaseController {
824
975
  }, shouldShowRequest));
825
976
  });
826
977
  }
827
- getTransaction(transactionID) {
978
+ getTransaction(transactionId) {
828
979
  const { transactions } = this.state;
829
- return transactions.find(({ id }) => id === transactionID);
980
+ return transactions.find(({ id }) => id === transactionId);
830
981
  }
831
982
  getApprovalId(txMeta) {
832
983
  return String(txMeta.id);
833
984
  }
834
- isTransactionCompleted(transactionid) {
835
- const transaction = this.getTransaction(transactionid);
985
+ isTransactionCompleted(transactionId) {
986
+ const transaction = this.getTransaction(transactionId);
836
987
  if (!transaction) {
837
988
  return { meta: undefined, isCompleted: false };
838
989
  }
@@ -879,7 +1030,7 @@ class TransactionController extends base_controller_1.BaseController {
879
1030
  const updatedTransactions = [
880
1031
  ...added,
881
1032
  ...currentTransactions.map((originalTransaction) => {
882
- const updatedTransaction = updated.find(({ transactionHash }) => transactionHash === originalTransaction.transactionHash);
1033
+ const updatedTransaction = updated.find(({ hash }) => hash === originalTransaction.hash);
883
1034
  return updatedTransaction !== null && updatedTransaction !== void 0 ? updatedTransaction : originalTransaction;
884
1035
  }),
885
1036
  ];
@@ -891,11 +1042,11 @@ class TransactionController extends base_controller_1.BaseController {
891
1042
  this.update({ lastFetchedBlockNumbers });
892
1043
  this.hub.emit('incomingTransactionBlock', blockNumber);
893
1044
  }
894
- generateDappSuggestedGasFees(transaction, origin) {
1045
+ generateDappSuggestedGasFees(txParams, origin) {
895
1046
  if (!origin || origin === controller_utils_1.ORIGIN_METAMASK) {
896
1047
  return undefined;
897
1048
  }
898
- const { gasPrice, maxFeePerGas, maxPriorityFeePerGas, gas } = transaction;
1049
+ const { gasPrice, maxFeePerGas, maxPriorityFeePerGas, gas } = txParams;
899
1050
  if (gasPrice === undefined &&
900
1051
  maxFeePerGas === undefined &&
901
1052
  maxPriorityFeePerGas === undefined &&
@@ -916,6 +1067,112 @@ class TransactionController extends base_controller_1.BaseController {
916
1067
  }
917
1068
  return dappSuggestedGasFees;
918
1069
  }
1070
+ /**
1071
+ * Validates and adds external provided transaction to state.
1072
+ *
1073
+ * @param transactionMeta - Nominated external transaction to be added to state.
1074
+ */
1075
+ addExternalTransaction(transactionMeta) {
1076
+ var _a, _b;
1077
+ return __awaiter(this, void 0, void 0, function* () {
1078
+ const { networkId, chainId } = this.getChainAndNetworkId();
1079
+ const { transactions } = this.state;
1080
+ const fromAddress = (_a = transactionMeta === null || transactionMeta === void 0 ? void 0 : transactionMeta.txParams) === null || _a === void 0 ? void 0 : _a.from;
1081
+ const sameFromAndNetworkTransactions = transactions.filter((transaction) => transaction.txParams.from === fromAddress &&
1082
+ (0, utils_1.transactionMatchesNetwork)(transaction, chainId, networkId));
1083
+ const confirmedTxs = sameFromAndNetworkTransactions.filter((transaction) => transaction.status === types_1.TransactionStatus.confirmed);
1084
+ const pendingTxs = sameFromAndNetworkTransactions.filter((transaction) => transaction.status === types_1.TransactionStatus.submitted);
1085
+ (0, external_transactions_1.validateConfirmedExternalTransaction)(transactionMeta, confirmedTxs, pendingTxs);
1086
+ // Make sure provided external transaction has non empty history array
1087
+ if (!((_b = transactionMeta.history) !== null && _b !== void 0 ? _b : []).length) {
1088
+ if (!this.isHistoryDisabled) {
1089
+ (0, history_1.addInitialHistorySnapshot)(transactionMeta);
1090
+ }
1091
+ }
1092
+ const updatedTransactions = [...transactions, transactionMeta];
1093
+ this.update({
1094
+ transactions: this.trimTransactionsForState(updatedTransactions),
1095
+ });
1096
+ });
1097
+ }
1098
+ /**
1099
+ * Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions
1100
+ * in the transactions have the same nonce.
1101
+ *
1102
+ * @param transactionId - Used to identify original transaction.
1103
+ */
1104
+ markNonceDuplicatesDropped(transactionId) {
1105
+ var _a, _b;
1106
+ const { networkId, chainId } = this.getChainAndNetworkId();
1107
+ const transactionMeta = this.getTransaction(transactionId);
1108
+ const nonce = (_a = transactionMeta === null || transactionMeta === void 0 ? void 0 : transactionMeta.txParams) === null || _a === void 0 ? void 0 : _a.nonce;
1109
+ const from = (_b = transactionMeta === null || transactionMeta === void 0 ? void 0 : transactionMeta.txParams) === null || _b === void 0 ? void 0 : _b.from;
1110
+ const sameNonceTxs = this.state.transactions.filter((transaction) => transaction.txParams.from === from &&
1111
+ transaction.txParams.nonce === nonce &&
1112
+ (0, utils_1.transactionMatchesNetwork)(transaction, chainId, networkId));
1113
+ if (!sameNonceTxs.length) {
1114
+ return;
1115
+ }
1116
+ // Mark all same nonce transactions as dropped and give it a replacedBy hash
1117
+ for (const transaction of sameNonceTxs) {
1118
+ if (transaction.id === transactionId) {
1119
+ continue;
1120
+ }
1121
+ transaction.replacedBy = transactionMeta === null || transactionMeta === void 0 ? void 0 : transactionMeta.hash;
1122
+ transaction.replacedById = transactionMeta === null || transactionMeta === void 0 ? void 0 : transactionMeta.id;
1123
+ // Drop any transaction that wasn't previously failed (off chain failure)
1124
+ if (transaction.status !== types_1.TransactionStatus.failed) {
1125
+ this.setTransactionStatusDropped(transaction);
1126
+ }
1127
+ }
1128
+ }
1129
+ /**
1130
+ * Method to set transaction status to dropped.
1131
+ *
1132
+ * @param transactionMeta - TransactionMeta of transaction to be marked as dropped.
1133
+ */
1134
+ setTransactionStatusDropped(transactionMeta) {
1135
+ transactionMeta.status = types_1.TransactionStatus.dropped;
1136
+ this.updateTransaction(transactionMeta, 'TransactionController#setTransactionStatusDropped - Transaction dropped');
1137
+ }
1138
+ /**
1139
+ * Get transaction with provided actionId.
1140
+ *
1141
+ * @param actionId - Unique ID to prevent duplicate requests
1142
+ * @returns the filtered transaction
1143
+ */
1144
+ getTransactionWithActionId(actionId) {
1145
+ return this.state.transactions.find((transaction) => actionId && transaction.actionId === actionId);
1146
+ }
1147
+ waitForTransactionFinished(transactionId) {
1148
+ return __awaiter(this, void 0, void 0, function* () {
1149
+ return new Promise((resolve) => {
1150
+ this.hub.once(`${transactionId}:finished`, (txMeta) => {
1151
+ resolve(txMeta);
1152
+ });
1153
+ });
1154
+ });
1155
+ }
1156
+ /**
1157
+ * Updates the r, s, and v properties of a TransactionMeta object
1158
+ * with values from a signed transaction.
1159
+ *
1160
+ * @param transactionMeta - The TransactionMeta object to update.
1161
+ * @param signedTx - The encompassing type for all transaction types containing r, s, and v values.
1162
+ */
1163
+ updateTransactionMetaRSV(transactionMeta, signedTx) {
1164
+ return __awaiter(this, void 0, void 0, function* () {
1165
+ if (signedTx.r) {
1166
+ transactionMeta.r = (0, ethereumjs_util_1.addHexPrefix)(signedTx.r.toString(16));
1167
+ }
1168
+ if (signedTx.s) {
1169
+ transactionMeta.s = (0, ethereumjs_util_1.addHexPrefix)(signedTx.s.toString(16));
1170
+ }
1171
+ if (signedTx.v) {
1172
+ transactionMeta.v = (0, ethereumjs_util_1.addHexPrefix)(signedTx.v.toString(16));
1173
+ }
1174
+ });
1175
+ }
919
1176
  }
920
1177
  exports.TransactionController = TransactionController;
921
1178
  exports.default = TransactionController;