@metamask-previews/transaction-controller 17.0.0-preview.5736db0 → 18.3.0-preview.3fbb6b41

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.
@@ -35,6 +35,7 @@ const external_transactions_1 = require("./utils/external-transactions");
35
35
  const gas_1 = require("./utils/gas");
36
36
  const gas_fees_1 = require("./utils/gas-fees");
37
37
  const history_1 = require("./utils/history");
38
+ const nonce_1 = require("./utils/nonce");
38
39
  const swaps_1 = require("./utils/swaps");
39
40
  const transaction_type_1 = require("./utils/transaction-type");
40
41
  const utils_1 = require("./utils/utils");
@@ -55,21 +56,23 @@ const controllerName = 'TransactionController';
55
56
  /**
56
57
  * Controller responsible for submitting and managing transactions.
57
58
  */
58
- class TransactionController extends base_controller_1.BaseController {
59
+ class TransactionController extends base_controller_1.BaseControllerV1 {
59
60
  /**
60
61
  * Creates a TransactionController instance.
61
62
  *
62
63
  * @param options - The controller options.
63
64
  * @param options.blockTracker - The block tracker used to poll for new blocks data.
65
+ * @param options.cancelMultiplier - Multiplier used to determine a transaction's increased gas fee during cancellation.
64
66
  * @param options.disableHistory - Whether to disable storing history in transaction metadata.
65
67
  * @param options.disableSendFlowHistory - Explicitly disable transaction metadata history.
66
68
  * @param options.disableSwaps - Whether to disable additional processing on swaps transactions.
67
- * @param options.getSavedGasFees - Gets the saved gas fee config.
68
69
  * @param options.getCurrentAccountEIP1559Compatibility - Whether or not the account supports EIP-1559.
69
70
  * @param options.getCurrentNetworkEIP1559Compatibility - Whether or not the network supports EIP-1559.
71
+ * @param options.getExternalPendingTransactions - Callback to retrieve pending transactions from external sources.
70
72
  * @param options.getGasFeeEstimates - Callback to retrieve gas fee estimates.
71
73
  * @param options.getNetworkState - Gets the state of the network controller.
72
74
  * @param options.getPermittedAccounts - Get accounts that a given origin has permissions for.
75
+ * @param options.getSavedGasFees - Gets the saved gas fee config.
73
76
  * @param options.getSelectedAddress - Gets the address of the currently selected account.
74
77
  * @param options.incomingTransactions - Configuration options for incoming transaction support.
75
78
  * @param options.incomingTransactions.includeTokenTransfers - Whether or not to include ERC20 token transfers.
@@ -82,6 +85,7 @@ class TransactionController extends base_controller_1.BaseController {
82
85
  * @param options.pendingTransactions.isResubmitEnabled - Whether transaction publishing is automatically retried.
83
86
  * @param options.provider - The provider used to create the underlying EthQuery instance.
84
87
  * @param options.securityProviderRequest - A function for verifying a transaction, whether it is malicious or not.
88
+ * @param options.speedUpMultiplier - Multiplier used to determine a transaction's increased gas fee during speed up.
85
89
  * @param options.hooks - The controller hooks.
86
90
  * @param options.hooks.afterSign - Additional logic to execute after signing a transaction. Return false to not change the status to signed.
87
91
  * @param options.hooks.beforeApproveOnInit - Additional logic to execute before starting an approval flow for a transaction during initialization. Return false to skip the transaction.
@@ -91,7 +95,7 @@ class TransactionController extends base_controller_1.BaseController {
91
95
  * @param config - Initial options used to configure this controller.
92
96
  * @param state - Initial state to set on this controller.
93
97
  */
94
- constructor({ blockTracker, disableHistory, disableSendFlowHistory, disableSwaps, getSavedGasFees, getCurrentAccountEIP1559Compatibility, getCurrentNetworkEIP1559Compatibility, getGasFeeEstimates, getNetworkState, getPermittedAccounts, getSelectedAddress, incomingTransactions = {}, messenger, onNetworkStateChange, pendingTransactions = {}, provider, securityProviderRequest, hooks = {}, }, config, state) {
98
+ constructor({ blockTracker, cancelMultiplier, disableHistory, disableSendFlowHistory, disableSwaps, getCurrentAccountEIP1559Compatibility, getCurrentNetworkEIP1559Compatibility, getExternalPendingTransactions, getGasFeeEstimates, getNetworkState, getPermittedAccounts, getSavedGasFees, getSelectedAddress, incomingTransactions = {}, messenger, onNetworkStateChange, pendingTransactions = {}, provider, securityProviderRequest, speedUpMultiplier, hooks = {}, }, config, state) {
95
99
  var _a, _b, _c, _d, _e;
96
100
  super(config, state);
97
101
  this.inProcessOfSigning = new Set();
@@ -123,14 +127,18 @@ class TransactionController extends base_controller_1.BaseController {
123
127
  this.registry = new eth_method_registry_1.default({ provider });
124
128
  this.getSavedGasFees = getSavedGasFees !== null && getSavedGasFees !== void 0 ? getSavedGasFees : ((_chainId) => undefined);
125
129
  this.getCurrentAccountEIP1559Compatibility =
126
- getCurrentAccountEIP1559Compatibility;
130
+ getCurrentAccountEIP1559Compatibility !== null && getCurrentAccountEIP1559Compatibility !== void 0 ? getCurrentAccountEIP1559Compatibility : (() => Promise.resolve(true));
127
131
  this.getCurrentNetworkEIP1559Compatibility =
128
132
  getCurrentNetworkEIP1559Compatibility;
129
133
  this.getGasFeeEstimates =
130
134
  getGasFeeEstimates || (() => Promise.resolve({}));
131
135
  this.getPermittedAccounts = getPermittedAccounts;
132
136
  this.getSelectedAddress = getSelectedAddress;
137
+ this.getExternalPendingTransactions =
138
+ getExternalPendingTransactions !== null && getExternalPendingTransactions !== void 0 ? getExternalPendingTransactions : (() => []);
133
139
  this.securityProviderRequest = securityProviderRequest;
140
+ this.cancelMultiplier = cancelMultiplier !== null && cancelMultiplier !== void 0 ? cancelMultiplier : exports.CANCEL_RATE;
141
+ this.speedUpMultiplier = speedUpMultiplier !== null && speedUpMultiplier !== void 0 ? speedUpMultiplier : exports.SPEED_UP_RATE;
134
142
  this.afterSign = (_a = hooks === null || hooks === void 0 ? void 0 : hooks.afterSign) !== null && _a !== void 0 ? _a : (() => true);
135
143
  this.beforeApproveOnInit = (_b = hooks === null || hooks === void 0 ? void 0 : hooks.beforeApproveOnInit) !== null && _b !== void 0 ? _b : (() => true);
136
144
  this.beforeCheckPendingTransaction =
@@ -144,8 +152,8 @@ class TransactionController extends base_controller_1.BaseController {
144
152
  // @ts-expect-error provider types misaligned: SafeEventEmitterProvider vs Record<string,string>
145
153
  provider,
146
154
  blockTracker,
147
- getPendingTransactions: (address) => (0, utils_1.getAndFormatTransactionsForNonceTracker)(address, types_1.TransactionStatus.submitted, this.state.transactions),
148
- getConfirmedTransactions: (address) => (0, utils_1.getAndFormatTransactionsForNonceTracker)(address, types_1.TransactionStatus.confirmed, this.state.transactions),
155
+ getPendingTransactions: this.getNonceTrackerPendingTransactions.bind(this),
156
+ getConfirmedTransactions: this.getNonceTrackerTransactions.bind(this, types_1.TransactionStatus.confirmed),
149
157
  });
150
158
  this.incomingTransactionHelper = new IncomingTransactionHelper_1.IncomingTransactionHelper({
151
159
  blockTracker,
@@ -251,7 +259,7 @@ class TransactionController extends base_controller_1.BaseController {
251
259
  */
252
260
  addTransaction(txParams, { actionId, deviceConfirmedOn, method, origin, requireApproval, securityAlertResponse, sendFlowHistory, swaps = {}, type, } = {}) {
253
261
  return __awaiter(this, void 0, void 0, function* () {
254
- const chainId = this.getChainId();
262
+ (0, logger_1.projectLogger)('Adding transaction', txParams);
255
263
  txParams = (0, utils_1.normalizeTxParams)(txParams);
256
264
  const isEIP1559Compatible = yield this.getEIP1559Compatibility();
257
265
  (0, validation_1.validateTxParams)(txParams, isEIP1559Compatible);
@@ -261,6 +269,7 @@ class TransactionController extends base_controller_1.BaseController {
261
269
  const dappSuggestedGasFees = this.generateDappSuggestedGasFees(txParams, origin);
262
270
  const transactionType = type !== null && type !== void 0 ? type : (yield (0, transaction_type_1.determineTransactionType)(txParams, this.ethQuery)).type;
263
271
  const existingTransactionMeta = this.getTransactionWithActionId(actionId);
272
+ const chainId = this.getChainId();
264
273
  // If a request to add a transaction with the same actionId is submitted again, a new transaction will not be created for it.
265
274
  const transactionMeta = existingTransactionMeta || {
266
275
  // Add actionId to txMeta to check if same actionId is seen again
@@ -333,15 +342,18 @@ class TransactionController extends base_controller_1.BaseController {
333
342
  * @param options.estimatedBaseFee - The estimated base fee of the transaction.
334
343
  */
335
344
  stopTransaction(transactionId, gasValues, { estimatedBaseFee, actionId, } = {}) {
336
- var _a, _b;
345
+ var _a, _b, _c;
337
346
  return __awaiter(this, void 0, void 0, function* () {
338
347
  // If transaction is found for same action id, do not create a cancel transaction.
339
348
  if (this.getTransactionWithActionId(actionId)) {
340
349
  return;
341
350
  }
342
351
  if (gasValues) {
352
+ // Not good practice to reassign a parameter but temporarily avoiding a larger refactor.
353
+ gasValues = (0, utils_1.normalizeGasFeeValues)(gasValues);
343
354
  (0, utils_1.validateGasValues)(gasValues);
344
355
  }
356
+ (0, logger_1.projectLogger)('Creating cancel transaction', transactionId, gasValues);
345
357
  const transactionMeta = this.getTransaction(transactionId);
346
358
  if (!transactionMeta) {
347
359
  return;
@@ -350,21 +362,21 @@ class TransactionController extends base_controller_1.BaseController {
350
362
  throw new Error('No sign method defined.');
351
363
  }
352
364
  // gasPrice (legacy non EIP1559)
353
- const minGasPrice = (0, utils_1.getIncreasedPriceFromExisting)(transactionMeta.txParams.gasPrice, exports.CANCEL_RATE);
365
+ const minGasPrice = (0, utils_1.getIncreasedPriceFromExisting)(transactionMeta.txParams.gasPrice, this.cancelMultiplier);
354
366
  const gasPriceFromValues = (0, utils_1.isGasPriceValue)(gasValues) && gasValues.gasPrice;
355
367
  const newGasPrice = (gasPriceFromValues &&
356
368
  (0, utils_1.validateMinimumIncrease)(gasPriceFromValues, minGasPrice)) ||
357
369
  minGasPrice;
358
370
  // maxFeePerGas (EIP1559)
359
371
  const existingMaxFeePerGas = (_a = transactionMeta.txParams) === null || _a === void 0 ? void 0 : _a.maxFeePerGas;
360
- const minMaxFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxFeePerGas, exports.CANCEL_RATE);
372
+ const minMaxFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxFeePerGas, this.cancelMultiplier);
361
373
  const maxFeePerGasValues = (0, utils_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxFeePerGas;
362
374
  const newMaxFeePerGas = (maxFeePerGasValues &&
363
375
  (0, utils_1.validateMinimumIncrease)(maxFeePerGasValues, minMaxFeePerGas)) ||
364
376
  (existingMaxFeePerGas && minMaxFeePerGas);
365
377
  // maxPriorityFeePerGas (EIP1559)
366
378
  const existingMaxPriorityFeePerGas = (_b = transactionMeta.txParams) === null || _b === void 0 ? void 0 : _b.maxPriorityFeePerGas;
367
- const minMaxPriorityFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxPriorityFeePerGas, exports.CANCEL_RATE);
379
+ const minMaxPriorityFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxPriorityFeePerGas, this.cancelMultiplier);
368
380
  const maxPriorityFeePerGasValues = (0, utils_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxPriorityFeePerGas;
369
381
  const newMaxPriorityFeePerGas = (maxPriorityFeePerGasValues &&
370
382
  (0, utils_1.validateMinimumIncrease)(maxPriorityFeePerGasValues, minMaxPriorityFeePerGas)) ||
@@ -375,7 +387,7 @@ class TransactionController extends base_controller_1.BaseController {
375
387
  gasLimit: transactionMeta.txParams.gas,
376
388
  maxFeePerGas: newMaxFeePerGas,
377
389
  maxPriorityFeePerGas: newMaxPriorityFeePerGas,
378
- type: '2',
390
+ type: types_1.TransactionEnvelopeType.feeMarket,
379
391
  nonce: transactionMeta.txParams.nonce,
380
392
  to: transactionMeta.txParams.from,
381
393
  value: '0x0',
@@ -391,6 +403,15 @@ class TransactionController extends base_controller_1.BaseController {
391
403
  const unsignedEthTx = this.prepareUnsignedEthTx(newTxParams);
392
404
  const signedTx = yield this.sign(unsignedEthTx, transactionMeta.txParams.from);
393
405
  const rawTx = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
406
+ const newFee = (_c = newTxParams.maxFeePerGas) !== null && _c !== void 0 ? _c : newTxParams.gasPrice;
407
+ const oldFee = newTxParams.maxFeePerGas
408
+ ? transactionMeta.txParams.maxFeePerGas
409
+ : transactionMeta.txParams.gasPrice;
410
+ (0, logger_1.projectLogger)('Submitting cancel transaction', {
411
+ oldFee,
412
+ newFee,
413
+ txParams: newTxParams,
414
+ });
394
415
  const hash = yield this.publishTransaction(rawTx);
395
416
  const cancelTransactionMeta = {
396
417
  actionId,
@@ -427,15 +448,18 @@ class TransactionController extends base_controller_1.BaseController {
427
448
  * @param options.estimatedBaseFee - The estimated base fee of the transaction.
428
449
  */
429
450
  speedUpTransaction(transactionId, gasValues, { actionId, estimatedBaseFee, } = {}) {
430
- var _a, _b;
451
+ var _a, _b, _c;
431
452
  return __awaiter(this, void 0, void 0, function* () {
432
453
  // If transaction is found for same action id, do not create a new speed up transaction.
433
454
  if (this.getTransactionWithActionId(actionId)) {
434
455
  return;
435
456
  }
436
457
  if (gasValues) {
458
+ // Not good practice to reassign a parameter but temporarily avoiding a larger refactor.
459
+ gasValues = (0, utils_1.normalizeGasFeeValues)(gasValues);
437
460
  (0, utils_1.validateGasValues)(gasValues);
438
461
  }
462
+ (0, logger_1.projectLogger)('Creating speed up transaction', transactionId, gasValues);
439
463
  const transactionMeta = this.state.transactions.find(({ id }) => id === transactionId);
440
464
  /* istanbul ignore next */
441
465
  if (!transactionMeta) {
@@ -446,31 +470,36 @@ class TransactionController extends base_controller_1.BaseController {
446
470
  throw new Error('No sign method defined.');
447
471
  }
448
472
  // gasPrice (legacy non EIP1559)
449
- const minGasPrice = (0, utils_1.getIncreasedPriceFromExisting)(transactionMeta.txParams.gasPrice, exports.SPEED_UP_RATE);
473
+ const minGasPrice = (0, utils_1.getIncreasedPriceFromExisting)(transactionMeta.txParams.gasPrice, this.speedUpMultiplier);
450
474
  const gasPriceFromValues = (0, utils_1.isGasPriceValue)(gasValues) && gasValues.gasPrice;
451
475
  const newGasPrice = (gasPriceFromValues &&
452
476
  (0, utils_1.validateMinimumIncrease)(gasPriceFromValues, minGasPrice)) ||
453
477
  minGasPrice;
454
478
  // maxFeePerGas (EIP1559)
455
479
  const existingMaxFeePerGas = (_a = transactionMeta.txParams) === null || _a === void 0 ? void 0 : _a.maxFeePerGas;
456
- const minMaxFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxFeePerGas, exports.SPEED_UP_RATE);
480
+ const minMaxFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxFeePerGas, this.speedUpMultiplier);
457
481
  const maxFeePerGasValues = (0, utils_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxFeePerGas;
458
482
  const newMaxFeePerGas = (maxFeePerGasValues &&
459
483
  (0, utils_1.validateMinimumIncrease)(maxFeePerGasValues, minMaxFeePerGas)) ||
460
484
  (existingMaxFeePerGas && minMaxFeePerGas);
461
485
  // maxPriorityFeePerGas (EIP1559)
462
486
  const existingMaxPriorityFeePerGas = (_b = transactionMeta.txParams) === null || _b === void 0 ? void 0 : _b.maxPriorityFeePerGas;
463
- const minMaxPriorityFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxPriorityFeePerGas, exports.SPEED_UP_RATE);
487
+ const minMaxPriorityFeePerGas = (0, utils_1.getIncreasedPriceFromExisting)(existingMaxPriorityFeePerGas, this.speedUpMultiplier);
464
488
  const maxPriorityFeePerGasValues = (0, utils_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxPriorityFeePerGas;
465
489
  const newMaxPriorityFeePerGas = (maxPriorityFeePerGasValues &&
466
490
  (0, utils_1.validateMinimumIncrease)(maxPriorityFeePerGasValues, minMaxPriorityFeePerGas)) ||
467
491
  (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);
468
492
  const txParams = newMaxFeePerGas && newMaxPriorityFeePerGas
469
- ? 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 });
493
+ ? Object.assign(Object.assign({}, transactionMeta.txParams), { gasLimit: transactionMeta.txParams.gas, maxFeePerGas: newMaxFeePerGas, maxPriorityFeePerGas: newMaxPriorityFeePerGas, type: types_1.TransactionEnvelopeType.feeMarket }) : Object.assign(Object.assign({}, transactionMeta.txParams), { gasLimit: transactionMeta.txParams.gas, gasPrice: newGasPrice });
470
494
  const unsignedEthTx = this.prepareUnsignedEthTx(txParams);
471
495
  const signedTx = yield this.sign(unsignedEthTx, transactionMeta.txParams.from);
472
496
  yield this.updateTransactionMetaRSV(transactionMeta, signedTx);
473
497
  const rawTx = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize());
498
+ const newFee = (_c = txParams.maxFeePerGas) !== null && _c !== void 0 ? _c : txParams.gasPrice;
499
+ const oldFee = txParams.maxFeePerGas
500
+ ? transactionMeta.txParams.maxFeePerGas
501
+ : transactionMeta.txParams.gasPrice;
502
+ (0, logger_1.projectLogger)('Submitting speed up transaction', { oldFee, newFee, txParams });
474
503
  const hash = yield (0, controller_utils_1.query)(this.ethQuery, 'sendRawTransaction', [rawTx]);
475
504
  const baseTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { estimatedBaseFee, id: (0, uuid_1.v1)(), time: Date.now(), hash,
476
505
  actionId, originalGasEstimate: transactionMeta.txParams.gas, type: types_1.TransactionType.retry, originalType: transactionMeta.type });
@@ -609,29 +638,15 @@ class TransactionController extends base_controller_1.BaseController {
609
638
  // Update external provided transaction with updated gas values and confirmed status.
610
639
  this.updateTransaction(transactionMeta, 'TransactionController:confirmExternalTransaction - Add external transaction');
611
640
  this.onTransactionStatusChange(transactionMeta);
612
- if (transactionMeta.type === types_1.TransactionType.swap) {
613
- (0, swaps_1.updatePostTransactionBalance)(transactionMeta, {
614
- ethQuery: this.ethQuery,
615
- getTransaction: this.getTransaction.bind(this),
616
- updateTransaction: this.updateTransaction.bind(this),
617
- })
618
- .then(({ updatedTransactionMeta, approvalTransactionMeta }) => {
619
- this.hub.emit('post-transaction-balance-updated', {
620
- transactionMeta: updatedTransactionMeta,
621
- approvalTransactionMeta,
622
- });
623
- })
624
- .catch((error) => {
625
- /* istanbul ignore next */
626
- (0, logger_1.projectLogger)('Error while updating post transaction balance', error);
627
- });
628
- }
641
+ // Intentional given potential duration of process.
642
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
643
+ this.updatePostBalance(transactionMeta);
629
644
  this.hub.emit('transaction-confirmed', {
630
645
  transactionMeta,
631
646
  });
632
647
  }
633
648
  catch (error) {
634
- console.error(error);
649
+ console.error('Failed to confirm external transaction', error);
635
650
  }
636
651
  });
637
652
  }
@@ -798,6 +813,9 @@ class TransactionController extends base_controller_1.BaseController {
798
813
  */
799
814
  approveTransactionsWithSameNonce(listOfTxParams = []) {
800
815
  return __awaiter(this, void 0, void 0, function* () {
816
+ (0, logger_1.projectLogger)('Approving transactions with same nonce', {
817
+ transactions: listOfTxParams,
818
+ });
801
819
  if (listOfTxParams.length === 0) {
802
820
  return '';
803
821
  }
@@ -817,6 +835,7 @@ class TransactionController extends base_controller_1.BaseController {
817
835
  const fromAddress = initialTx.from;
818
836
  nonceLock = yield this.nonceTracker.getNonceLock(fromAddress);
819
837
  const nonce = nonceLock.nextNonce;
838
+ (0, logger_1.projectLogger)('Using nonce from nonce tracker', nonce, nonceLock.nonceDetails);
820
839
  rawTransactions = yield Promise.all(listOfTxParams.map((txParams) => {
821
840
  txParams.nonce = (0, ethereumjs_util_1.addHexPrefix)(nonce.toString(16));
822
841
  return this.signExternalTransaction(txParams);
@@ -842,14 +861,12 @@ class TransactionController extends base_controller_1.BaseController {
842
861
  *
843
862
  * @param transactionId - The ID of the transaction to update.
844
863
  * @param options - The custodial transaction options to update.
845
- * @param options.custodyStatus - The new custody status value to be assigned.
846
864
  * @param options.errorMessage - The error message to be assigned in case transaction status update to failed.
847
865
  * @param options.hash - The new hash value to be assigned.
848
866
  * @param options.status - The new status value to be assigned.
849
867
  */
850
- updateCustodialTransaction(transactionId, { custodyStatus, errorMessage, hash, status, }) {
851
- let transactionMeta;
852
- transactionMeta = this.getTransaction(transactionId);
868
+ updateCustodialTransaction(transactionId, { errorMessage, hash, status, }) {
869
+ const transactionMeta = this.getTransaction(transactionId);
853
870
  if (!transactionMeta) {
854
871
  throw new Error(`Cannot update custodial transaction as no transaction metadata found`);
855
872
  }
@@ -864,23 +881,112 @@ class TransactionController extends base_controller_1.BaseController {
864
881
  ].includes(status)) {
865
882
  throw new Error(`Cannot update custodial transaction with status: ${status}`);
866
883
  }
867
- if (status === types_1.TransactionStatus.signed) {
868
- transactionMeta.status = status;
869
- }
884
+ const updatedTransactionMeta = (0, lodash_1.merge)(transactionMeta, (0, lodash_1.pickBy)({ hash, status }));
870
885
  if (status === types_1.TransactionStatus.submitted) {
871
- transactionMeta.submittedTime = new Date().getTime();
872
- transactionMeta.status = status;
886
+ updatedTransactionMeta.submittedTime = new Date().getTime();
873
887
  }
874
888
  if (status === types_1.TransactionStatus.failed) {
875
- transactionMeta = Object.assign(Object.assign({}, transactionMeta), { error: (0, utils_1.normalizeTxError)(new Error(errorMessage)), status: types_1.TransactionStatus.failed });
889
+ updatedTransactionMeta.error = (0, utils_1.normalizeTxError)(new Error(errorMessage));
876
890
  }
877
- if (custodyStatus) {
878
- transactionMeta.custodyStatus = custodyStatus;
891
+ this.updateTransaction(updatedTransactionMeta, `TransactionController:updateCustodialTransaction - Custodial transaction updated`);
892
+ }
893
+ /**
894
+ * Creates approvals for all unapproved transactions persisted.
895
+ */
896
+ initApprovals() {
897
+ const chainId = this.getChainId();
898
+ const unapprovedTxs = this.state.transactions.filter((transaction) => transaction.status === types_1.TransactionStatus.unapproved &&
899
+ transaction.chainId === chainId);
900
+ for (const txMeta of unapprovedTxs) {
901
+ this.processApproval(txMeta, {
902
+ shouldShowRequest: false,
903
+ }).catch((error) => {
904
+ if ((error === null || error === void 0 ? void 0 : error.code) === rpc_errors_1.errorCodes.provider.userRejectedRequest) {
905
+ return;
906
+ }
907
+ console.error('Error during persisted transaction approval', error);
908
+ });
879
909
  }
880
- if (hash) {
881
- transactionMeta.hash = hash;
910
+ }
911
+ /**
912
+ * Search transaction metadata for matching entries.
913
+ *
914
+ * @param opts - Options bag.
915
+ * @param opts.searchCriteria - An object containing values or functions for transaction properties to filter transactions with.
916
+ * @param opts.initialList - The transactions to search. Defaults to the current state.
917
+ * @param opts.filterToCurrentNetwork - Whether to filter the results to the current network. Defaults to true.
918
+ * @param opts.limit - The maximum number of transactions to return. No limit by default.
919
+ * @returns An array of transactions matching the provided options.
920
+ */
921
+ getTransactions({ searchCriteria = {}, initialList, filterToCurrentNetwork = true, limit, } = {}) {
922
+ const chainId = this.getChainId();
923
+ // searchCriteria is an object that might have values that aren't predicate
924
+ // methods. When providing any other value type (string, number, etc), we
925
+ // consider this shorthand for "check the value at key for strict equality
926
+ // with the provided value". To conform this object to be only methods, we
927
+ // mapValues (lodash) such that every value on the object is a method that
928
+ // returns a boolean.
929
+ const predicateMethods = (0, lodash_1.mapValues)(searchCriteria, (predicate) => {
930
+ return typeof predicate === 'function'
931
+ ? predicate
932
+ : (v) => v === predicate;
933
+ });
934
+ const transactionsToFilter = initialList !== null && initialList !== void 0 ? initialList : this.state.transactions;
935
+ // Combine sortBy and pickBy to transform our state object into an array of
936
+ // matching transactions that are sorted by time.
937
+ const filteredTransactions = (0, lodash_1.sortBy)((0, lodash_1.pickBy)(transactionsToFilter, (transaction) => {
938
+ if (filterToCurrentNetwork && transaction.chainId !== chainId) {
939
+ return false;
940
+ }
941
+ // iterate over the predicateMethods keys to check if the transaction
942
+ // matches the searchCriteria
943
+ for (const [key, predicate] of Object.entries(predicateMethods)) {
944
+ // We return false early as soon as we know that one of the specified
945
+ // search criteria do not match the transaction. This prevents
946
+ // needlessly checking all criteria when we already know the criteria
947
+ // are not fully satisfied. We check both txParams and the base
948
+ // object as predicate keys can be either.
949
+ if (key in transaction.txParams) {
950
+ if (predicate(transaction.txParams[key]) === false) {
951
+ return false;
952
+ }
953
+ }
954
+ else if (predicate(transaction[key]) === false) {
955
+ return false;
956
+ }
957
+ }
958
+ return true;
959
+ }), 'time');
960
+ if (limit !== undefined) {
961
+ // We need to have all transactions of a given nonce in order to display
962
+ // necessary details in the UI. We use the size of this set to determine
963
+ // whether we have reached the limit provided, thus ensuring that all
964
+ // transactions of nonces we include will be sent to the UI.
965
+ const nonces = new Set();
966
+ const txs = [];
967
+ // By default, the transaction list we filter from is sorted by time ASC.
968
+ // To ensure that filtered results prefers the newest transactions we
969
+ // iterate from right to left, inserting transactions into front of a new
970
+ // array. The original order is preserved, but we ensure that newest txs
971
+ // are preferred.
972
+ for (let i = filteredTransactions.length - 1; i > -1; i--) {
973
+ const txMeta = filteredTransactions[i];
974
+ const { nonce } = txMeta.txParams;
975
+ if (!nonces.has(nonce)) {
976
+ if (nonces.size < limit) {
977
+ nonces.add(nonce);
978
+ }
979
+ else {
980
+ continue;
981
+ }
982
+ }
983
+ // Push transaction into the beginning of our array to ensure the
984
+ // original order is preserved.
985
+ txs.unshift(txMeta);
986
+ }
987
+ return txs;
882
988
  }
883
- this.updateTransaction(transactionMeta, `TransactionController:updateCustodialTransaction - Custodial transaction updated`);
989
+ return filteredTransactions;
884
990
  }
885
991
  signExternalTransaction(transactionParams) {
886
992
  return __awaiter(this, void 0, void 0, function* () {
@@ -915,7 +1021,8 @@ class TransactionController extends base_controller_1.BaseController {
915
1021
  }
916
1022
  updateGasProperties(transactionMeta) {
917
1023
  return __awaiter(this, void 0, void 0, function* () {
918
- const isEIP1559Compatible = yield this.getEIP1559Compatibility();
1024
+ const isEIP1559Compatible = (yield this.getEIP1559Compatibility()) &&
1025
+ transactionMeta.txParams.type !== types_1.TransactionEnvelopeType.legacy;
919
1026
  const chainId = this.getChainId();
920
1027
  yield (0, gas_1.updateGas)({
921
1028
  ethQuery: this.ethQuery,
@@ -936,8 +1043,6 @@ class TransactionController extends base_controller_1.BaseController {
936
1043
  return this.state.transactions.filter((transaction) => transaction.status === status && transaction.chainId === chainId);
937
1044
  }
938
1045
  onBootCleanup() {
939
- this.createApprovalsForUnapprovedTransactions();
940
- this.loadGasValuesForUnapprovedTransactions();
941
1046
  this.submitApprovedTransactions();
942
1047
  }
943
1048
  /**
@@ -957,26 +1062,6 @@ class TransactionController extends base_controller_1.BaseController {
957
1062
  });
958
1063
  }
959
1064
  }
960
- /**
961
- * Update the gas values of all unapproved transactions on current chain.
962
- */
963
- loadGasValuesForUnapprovedTransactions() {
964
- return __awaiter(this, void 0, void 0, function* () {
965
- const unapprovedTransactions = this.getCurrentChainTransactionsByStatus(types_1.TransactionStatus.unapproved);
966
- const results = yield Promise.allSettled(unapprovedTransactions.map((transactionMeta) => __awaiter(this, void 0, void 0, function* () {
967
- yield this.updateGasProperties(transactionMeta);
968
- this.updateTransaction(transactionMeta, 'TransactionController:loadGasValuesForUnapprovedTransactions - Gas values updated');
969
- })));
970
- for (const [index, result] of results.entries()) {
971
- if (result.status === 'rejected') {
972
- const transactionMeta = unapprovedTransactions[index];
973
- this.failTransaction(transactionMeta, result.reason);
974
- /* istanbul ignore next */
975
- console.error('Error while loading gas values for persisted transaction id: ', transactionMeta.id, result.reason);
976
- }
977
- }
978
- });
979
- }
980
1065
  /**
981
1066
  * Force to submit approved transactions on current chain.
982
1067
  */
@@ -1006,6 +1091,22 @@ class TransactionController extends base_controller_1.BaseController {
1006
1091
  shouldShowRequest,
1007
1092
  });
1008
1093
  resultCallbacks = acceptResult.resultCallbacks;
1094
+ if (resultCallbacks) {
1095
+ this.hub.once(`${transactionId}:publish-skip`, () => {
1096
+ resultCallbacks === null || resultCallbacks === void 0 ? void 0 : resultCallbacks.success();
1097
+ // Remove the reference to prevent additional reports once submitted.
1098
+ resultCallbacks = undefined;
1099
+ });
1100
+ }
1101
+ const approvalValue = acceptResult.value;
1102
+ const updatedTransaction = approvalValue === null || approvalValue === void 0 ? void 0 : approvalValue.txMeta;
1103
+ if (updatedTransaction) {
1104
+ (0, logger_1.projectLogger)('Updating transaction with approval data', {
1105
+ customNonce: updatedTransaction.customNonceValue,
1106
+ params: updatedTransaction.txParams,
1107
+ });
1108
+ this.updateTransaction(updatedTransaction, 'TransactionController#processApproval - Updated with approval data');
1109
+ }
1009
1110
  }
1010
1111
  const { isCompleted: isTxCompleted } = this.isTransactionCompleted(transactionId);
1011
1112
  if (!isTxCompleted) {
@@ -1060,8 +1161,8 @@ class TransactionController extends base_controller_1.BaseController {
1060
1161
  const chainId = this.getChainId();
1061
1162
  const index = transactions.findIndex(({ id }) => transactionId === id);
1062
1163
  const transactionMeta = transactions[index];
1063
- const { txParams: { nonce, from }, } = transactionMeta;
1064
- let nonceLock;
1164
+ const { txParams: { from }, } = transactionMeta;
1165
+ let releaseNonceLock;
1065
1166
  try {
1066
1167
  if (!this.sign) {
1067
1168
  releaseLock();
@@ -1077,37 +1178,35 @@ class TransactionController extends base_controller_1.BaseController {
1077
1178
  (0, logger_1.projectLogger)('Skipping approval as signing in progress', transactionId);
1078
1179
  return;
1079
1180
  }
1080
- let nonceToUse = nonce;
1081
- // if a nonce already exists on the transactionMeta it means this is a speedup or cancel transaction
1082
- // so we want to reuse that nonce and hope that it beats the previous attempt to chain. Otherwise use a new locked nonce
1083
- if (!nonceToUse) {
1084
- nonceLock = yield this.nonceTracker.getNonceLock(from);
1085
- nonceToUse = (0, ethereumjs_util_1.addHexPrefix)(nonceLock.nextNonce.toString(16));
1086
- }
1181
+ const [nonce, releaseNonce] = yield (0, nonce_1.getNextNonce)(transactionMeta, this.nonceTracker);
1182
+ releaseNonceLock = releaseNonce;
1087
1183
  transactionMeta.status = types_1.TransactionStatus.approved;
1088
- transactionMeta.txParams.nonce = nonceToUse;
1184
+ transactionMeta.txParams.nonce = nonce;
1089
1185
  transactionMeta.txParams.chainId = chainId;
1090
1186
  const baseTxParams = Object.assign(Object.assign({}, transactionMeta.txParams), { gasLimit: transactionMeta.txParams.gas });
1091
1187
  this.updateTransaction(transactionMeta, 'TransactionController#approveTransaction - Transaction approved');
1092
1188
  this.onTransactionStatusChange(transactionMeta);
1093
1189
  const isEIP1559 = (0, utils_1.isEIP1559Transaction)(transactionMeta.txParams);
1094
1190
  const txParams = isEIP1559
1095
- ? Object.assign(Object.assign({}, baseTxParams), { maxFeePerGas: transactionMeta.txParams.maxFeePerGas, maxPriorityFeePerGas: transactionMeta.txParams.maxPriorityFeePerGas, estimatedBaseFee: transactionMeta.txParams.estimatedBaseFee,
1096
- // specify type 2 if maxFeePerGas and maxPriorityFeePerGas are set
1097
- type: '2' }) : baseTxParams;
1098
- // delete gasPrice if maxFeePerGas and maxPriorityFeePerGas are set
1099
- if (isEIP1559) {
1100
- delete txParams.gasPrice;
1101
- }
1102
- const rawTx = yield this.signTransaction(transactionMeta);
1191
+ ? Object.assign(Object.assign({}, baseTxParams), { estimatedBaseFee: transactionMeta.txParams.estimatedBaseFee, type: types_1.TransactionEnvelopeType.feeMarket }) : baseTxParams;
1192
+ const rawTx = yield this.signTransaction(transactionMeta, txParams);
1103
1193
  if (!this.beforePublish(transactionMeta)) {
1104
1194
  (0, logger_1.projectLogger)('Skipping publishing transaction based on hook');
1195
+ this.hub.emit(`${transactionMeta.id}:publish-skip`, transactionMeta);
1105
1196
  return;
1106
1197
  }
1107
1198
  if (!rawTx) {
1108
1199
  return;
1109
1200
  }
1201
+ if (transactionMeta.type === types_1.TransactionType.swap) {
1202
+ (0, logger_1.projectLogger)('Determining pre-transaction balance');
1203
+ const preTxBalance = yield (0, controller_utils_1.query)(this.ethQuery, 'getBalance', [from]);
1204
+ transactionMeta.preTxBalance = preTxBalance;
1205
+ (0, logger_1.projectLogger)('Updated pre-transaction balance', transactionMeta.preTxBalance);
1206
+ }
1207
+ (0, logger_1.projectLogger)('Publishing transaction', txParams);
1110
1208
  const hash = yield this.publishTransaction(rawTx);
1209
+ (0, logger_1.projectLogger)('Publish successful', hash);
1111
1210
  transactionMeta.hash = hash;
1112
1211
  transactionMeta.status = types_1.TransactionStatus.submitted;
1113
1212
  transactionMeta.submittedTime = new Date().getTime();
@@ -1124,9 +1223,7 @@ class TransactionController extends base_controller_1.BaseController {
1124
1223
  finally {
1125
1224
  this.inProcessOfSigning.delete(transactionId);
1126
1225
  // must set transaction to submitted/failed before releasing lock
1127
- if (nonceLock) {
1128
- nonceLock.releaseLock();
1129
- }
1226
+ releaseNonceLock === null || releaseNonceLock === void 0 ? void 0 : releaseNonceLock();
1130
1227
  releaseLock();
1131
1228
  }
1132
1229
  });
@@ -1167,7 +1264,7 @@ class TransactionController extends base_controller_1.BaseController {
1167
1264
  * representation, this function will not break apart transactions with the
1168
1265
  * same nonce, created on the same day, per network. Not accounting for transactions of the same
1169
1266
  * nonce, same day and network combo can result in confusing or broken experiences
1170
- * in the UI. The transactions are then updated using the BaseController update.
1267
+ * in the UI. The transactions are then updated using the BaseControllerV1 update.
1171
1268
  *
1172
1269
  * @param transactions - The transactions to be applied to the state.
1173
1270
  * @returns The trimmed list of transactions.
@@ -1331,25 +1428,23 @@ class TransactionController extends base_controller_1.BaseController {
1331
1428
  */
1332
1429
  addExternalTransaction(transactionMeta) {
1333
1430
  var _a, _b;
1334
- return __awaiter(this, void 0, void 0, function* () {
1335
- const chainId = this.getChainId();
1336
- const { transactions } = this.state;
1337
- const fromAddress = (_a = transactionMeta === null || transactionMeta === void 0 ? void 0 : transactionMeta.txParams) === null || _a === void 0 ? void 0 : _a.from;
1338
- const sameFromAndNetworkTransactions = transactions.filter((transaction) => transaction.txParams.from === fromAddress &&
1339
- transaction.chainId === chainId);
1340
- const confirmedTxs = sameFromAndNetworkTransactions.filter((transaction) => transaction.status === types_1.TransactionStatus.confirmed);
1341
- const pendingTxs = sameFromAndNetworkTransactions.filter((transaction) => transaction.status === types_1.TransactionStatus.submitted);
1342
- (0, external_transactions_1.validateConfirmedExternalTransaction)(transactionMeta, confirmedTxs, pendingTxs);
1343
- // Make sure provided external transaction has non empty history array
1344
- if (!((_b = transactionMeta.history) !== null && _b !== void 0 ? _b : []).length) {
1345
- if (!this.isHistoryDisabled) {
1346
- (0, history_1.addInitialHistorySnapshot)(transactionMeta);
1347
- }
1431
+ const chainId = this.getChainId();
1432
+ const { transactions } = this.state;
1433
+ const fromAddress = (_a = transactionMeta === null || transactionMeta === void 0 ? void 0 : transactionMeta.txParams) === null || _a === void 0 ? void 0 : _a.from;
1434
+ const sameFromAndNetworkTransactions = transactions.filter((transaction) => transaction.txParams.from === fromAddress &&
1435
+ transaction.chainId === chainId);
1436
+ const confirmedTxs = sameFromAndNetworkTransactions.filter((transaction) => transaction.status === types_1.TransactionStatus.confirmed);
1437
+ const pendingTxs = sameFromAndNetworkTransactions.filter((transaction) => transaction.status === types_1.TransactionStatus.submitted);
1438
+ (0, external_transactions_1.validateConfirmedExternalTransaction)(transactionMeta, confirmedTxs, pendingTxs);
1439
+ // Make sure provided external transaction has non empty history array
1440
+ if (!((_b = transactionMeta.history) !== null && _b !== void 0 ? _b : []).length) {
1441
+ if (!this.isHistoryDisabled) {
1442
+ (0, history_1.addInitialHistorySnapshot)(transactionMeta);
1348
1443
  }
1349
- const updatedTransactions = [...transactions, transactionMeta];
1350
- this.update({
1351
- transactions: this.trimTransactionsForState(updatedTransactions),
1352
- });
1444
+ }
1445
+ const updatedTransactions = [...transactions, transactionMeta];
1446
+ this.update({
1447
+ transactions: this.trimTransactionsForState(updatedTransactions),
1353
1448
  });
1354
1449
  }
1355
1450
  /**
@@ -1435,27 +1530,22 @@ class TransactionController extends base_controller_1.BaseController {
1435
1530
  });
1436
1531
  }
1437
1532
  getEIP1559Compatibility() {
1438
- var _a, _b;
1439
1533
  return __awaiter(this, void 0, void 0, function* () {
1440
1534
  const currentNetworkIsEIP1559Compatible = yield this.getCurrentNetworkEIP1559Compatibility();
1441
- const currentAccountIsEIP1559Compatible = (_b = (_a = this.getCurrentAccountEIP1559Compatibility) === null || _a === void 0 ? void 0 : _a.call(this)) !== null && _b !== void 0 ? _b : true;
1535
+ const currentAccountIsEIP1559Compatible = yield this.getCurrentAccountEIP1559Compatibility();
1442
1536
  return (currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible);
1443
1537
  });
1444
1538
  }
1445
1539
  addPendingTransactionTrackerListeners() {
1446
- this.pendingTransactionTracker.hub.on('transaction-confirmed', (transactionMeta) => {
1447
- this.hub.emit('transaction-confirmed', { transactionMeta });
1448
- this.hub.emit(`${transactionMeta.id}:confirmed`, transactionMeta);
1449
- this.onTransactionStatusChange(transactionMeta);
1450
- });
1540
+ this.pendingTransactionTracker.hub.on('transaction-confirmed', this.onConfirmedTransaction.bind(this));
1451
1541
  this.pendingTransactionTracker.hub.on('transaction-dropped', this.setTransactionStatusDropped.bind(this));
1452
1542
  this.pendingTransactionTracker.hub.on('transaction-failed', this.failTransaction.bind(this));
1453
1543
  this.pendingTransactionTracker.hub.on('transaction-updated', this.updateTransaction.bind(this));
1454
1544
  }
1455
- signTransaction(transactionMeta) {
1545
+ signTransaction(transactionMeta, txParams) {
1456
1546
  var _a;
1457
1547
  return __awaiter(this, void 0, void 0, function* () {
1458
- const { txParams } = transactionMeta;
1548
+ (0, logger_1.projectLogger)('Signing transaction', txParams);
1459
1549
  const unsignedEthTx = this.prepareUnsignedEthTx(txParams);
1460
1550
  this.inProcessOfSigning.add(transactionMeta.id);
1461
1551
  const signedTx = yield ((_a = this.sign) === null || _a === void 0 ? void 0 : _a.call(this, unsignedEthTx, txParams.from, ...this.getAdditionalSignArguments(transactionMeta)));
@@ -1464,6 +1554,7 @@ class TransactionController extends base_controller_1.BaseController {
1464
1554
  return undefined;
1465
1555
  }
1466
1556
  if (!this.afterSign(transactionMeta, signedTx)) {
1557
+ this.updateTransaction(transactionMeta, 'TransactionController#signTransaction - Update after sign');
1467
1558
  (0, logger_1.projectLogger)('Skipping signed status based on hook');
1468
1559
  return undefined;
1469
1560
  }
@@ -1480,7 +1571,47 @@ class TransactionController extends base_controller_1.BaseController {
1480
1571
  onTransactionStatusChange(transactionMeta) {
1481
1572
  this.hub.emit('transaction-status-update', { transactionMeta });
1482
1573
  }
1574
+ getNonceTrackerPendingTransactions(address) {
1575
+ const standardPendingTransactions = this.getNonceTrackerTransactions(types_1.TransactionStatus.submitted, address);
1576
+ const externalPendingTransactions = this.getExternalPendingTransactions(address);
1577
+ return [...standardPendingTransactions, ...externalPendingTransactions];
1578
+ }
1579
+ getNonceTrackerTransactions(status, address) {
1580
+ const currentChainId = this.getChainId();
1581
+ return (0, nonce_1.getAndFormatTransactionsForNonceTracker)(currentChainId, address, status, this.state.transactions);
1582
+ }
1583
+ onConfirmedTransaction(transactionMeta) {
1584
+ (0, logger_1.projectLogger)('Processing confirmed transaction', transactionMeta.id);
1585
+ this.markNonceDuplicatesDropped(transactionMeta.id);
1586
+ this.hub.emit('transaction-confirmed', { transactionMeta });
1587
+ this.hub.emit(`${transactionMeta.id}:confirmed`, transactionMeta);
1588
+ this.onTransactionStatusChange(transactionMeta);
1589
+ // Intentional given potential duration of process.
1590
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1591
+ this.updatePostBalance(transactionMeta);
1592
+ }
1593
+ updatePostBalance(transactionMeta) {
1594
+ return __awaiter(this, void 0, void 0, function* () {
1595
+ try {
1596
+ if (transactionMeta.type !== types_1.TransactionType.swap) {
1597
+ return;
1598
+ }
1599
+ const { updatedTransactionMeta, approvalTransactionMeta } = yield (0, swaps_1.updatePostTransactionBalance)(transactionMeta, {
1600
+ ethQuery: this.ethQuery,
1601
+ getTransaction: this.getTransaction.bind(this),
1602
+ updateTransaction: this.updateTransaction.bind(this),
1603
+ });
1604
+ this.hub.emit('post-transaction-balance-updated', {
1605
+ transactionMeta: updatedTransactionMeta,
1606
+ approvalTransactionMeta,
1607
+ });
1608
+ }
1609
+ catch (error) {
1610
+ /* istanbul ignore next */
1611
+ (0, logger_1.projectLogger)('Error while updating post transaction balance', error);
1612
+ }
1613
+ });
1614
+ }
1483
1615
  }
1484
1616
  exports.TransactionController = TransactionController;
1485
- exports.default = TransactionController;
1486
1617
  //# sourceMappingURL=TransactionController.js.map