@metamask/transaction-controller 13.0.0 → 14.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.
@@ -30,9 +30,12 @@ const EtherscanRemoteTransactionSource_1 = require("./EtherscanRemoteTransaction
30
30
  const external_transactions_1 = require("./external-transactions");
31
31
  const history_1 = require("./history");
32
32
  const IncomingTransactionHelper_1 = require("./IncomingTransactionHelper");
33
+ const logger_1 = require("./logger");
34
+ const PendingTransactionTracker_1 = require("./PendingTransactionTracker");
33
35
  const transaction_type_1 = require("./transaction-type");
34
36
  const types_1 = require("./types");
35
37
  const utils_1 = require("./utils");
38
+ const validation_1 = require("./validation");
36
39
  exports.HARDFORK = common_1.Hardfork.London;
37
40
  /**
38
41
  * Multiplier used to determine a transaction's increased gas fee during cancellation
@@ -60,6 +63,7 @@ class TransactionController extends base_controller_1.BaseController {
60
63
  * @param options.getCurrentAccountEIP1559Compatibility - Whether or not the account supports EIP-1559.
61
64
  * @param options.getCurrentNetworkEIP1559Compatibility - Whether or not the network supports EIP-1559.
62
65
  * @param options.getNetworkState - Gets the state of the network controller.
66
+ * @param options.getPermittedAccounts - Get accounts that a given origin has permissions for.
63
67
  * @param options.getSelectedAddress - Gets the address of the currently selected account.
64
68
  * @param options.incomingTransactions - Configuration options for incoming transaction support.
65
69
  * @param options.incomingTransactions.includeTokenTransfers - Whether or not to include ERC20 token transfers.
@@ -69,10 +73,11 @@ class TransactionController extends base_controller_1.BaseController {
69
73
  * @param options.messenger - The controller messenger.
70
74
  * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.
71
75
  * @param options.provider - The provider used to create the underlying EthQuery instance.
76
+ * @param options.securityProviderRequest - A function for verifying a transaction, whether it is malicious or not.
72
77
  * @param config - Initial options used to configure this controller.
73
78
  * @param state - Initial state to set on this controller.
74
79
  */
75
- constructor({ blockTracker, disableHistory, disableSendFlowHistory, getCurrentAccountEIP1559Compatibility, getCurrentNetworkEIP1559Compatibility, getNetworkState, getSelectedAddress, incomingTransactions = {}, messenger, onNetworkStateChange, provider, }, config, state) {
80
+ constructor({ blockTracker, disableHistory, disableSendFlowHistory, getCurrentAccountEIP1559Compatibility, getCurrentNetworkEIP1559Compatibility, getNetworkState, getPermittedAccounts, getSelectedAddress, incomingTransactions = {}, messenger, onNetworkStateChange, provider, securityProviderRequest, }, config, state) {
76
81
  super(config, state);
77
82
  this.mutex = new async_mutex_1.Mutex();
78
83
  /**
@@ -84,7 +89,6 @@ class TransactionController extends base_controller_1.BaseController {
84
89
  */
85
90
  this.name = 'TransactionController';
86
91
  this.defaultConfig = {
87
- interval: 15000,
88
92
  txHistoryLimit: 40,
89
93
  };
90
94
  this.defaultState = {
@@ -104,6 +108,9 @@ class TransactionController extends base_controller_1.BaseController {
104
108
  getCurrentAccountEIP1559Compatibility;
105
109
  this.getCurrentNetworkEIP1559Compatibility =
106
110
  getCurrentNetworkEIP1559Compatibility;
111
+ this.getPermittedAccounts = getPermittedAccounts;
112
+ this.getSelectedAddress = getSelectedAddress;
113
+ this.securityProviderRequest = securityProviderRequest;
107
114
  this.nonceTracker = new nonce_tracker_1.default({
108
115
  provider,
109
116
  blockTracker,
@@ -125,11 +132,20 @@ class TransactionController extends base_controller_1.BaseController {
125
132
  });
126
133
  this.incomingTransactionHelper.hub.on('transactions', this.onIncomingTransactions.bind(this));
127
134
  this.incomingTransactionHelper.hub.on('updatedLastFetchedBlockNumbers', this.onUpdatedLastFetchedBlockNumbers.bind(this));
135
+ this.pendingTransactionTracker = new PendingTransactionTracker_1.PendingTransactionTracker({
136
+ blockTracker,
137
+ failTransaction: this.failTransaction.bind(this),
138
+ getChainId: this.getChainId.bind(this),
139
+ getEthQuery: () => this.ethQuery,
140
+ getTransactions: () => this.state.transactions,
141
+ });
142
+ this.pendingTransactionTracker.hub.on('transactions', this.onPendingTransactionsUpdate.bind(this));
143
+ this.pendingTransactionTracker.hub.on('transaction-confirmed', (transactionMeta) => this.hub.emit(`${transactionMeta.id}:confirmed`, transactionMeta));
128
144
  onNetworkStateChange(() => {
129
145
  this.ethQuery = new eth_query_1.default(this.provider);
130
146
  this.registry = new eth_method_registry_1.default({ provider: this.provider });
131
147
  });
132
- this.poll();
148
+ this.pendingTransactionTracker.start();
133
149
  }
134
150
  failTransaction(transactionMeta, error) {
135
151
  const newTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { error, status: types_1.TransactionStatus.failed });
@@ -143,21 +159,6 @@ class TransactionController extends base_controller_1.BaseController {
143
159
  return { registryMethod, parsedRegistryMethod };
144
160
  });
145
161
  }
146
- /**
147
- * Starts a new polling interval.
148
- *
149
- * @param interval - The polling interval used to fetch new transaction statuses.
150
- */
151
- poll(interval) {
152
- return __awaiter(this, void 0, void 0, function* () {
153
- interval && this.configure({ interval }, false, false);
154
- this.handle && clearTimeout(this.handle);
155
- yield (0, controller_utils_1.safelyExecute)(() => this.queryTransactionStatuses());
156
- this.handle = setTimeout(() => {
157
- this.poll(this.config.interval);
158
- }, this.config.interval);
159
- });
160
- }
161
162
  /**
162
163
  * Handle new method data request.
163
164
  *
@@ -193,6 +194,7 @@ class TransactionController extends base_controller_1.BaseController {
193
194
  * @param opts - Additional options to control how the transaction is added.
194
195
  * @param opts.actionId - Unique ID to prevent duplicate requests.
195
196
  * @param opts.deviceConfirmedOn - An enum to indicate what device confirmed the transaction.
197
+ * @param opts.method - RPC method that requested the transaction.
196
198
  * @param opts.origin - The origin of the transaction request, such as a dApp hostname.
197
199
  * @param opts.requireApproval - Whether the transaction requires approval by the user, defaults to true unless explicitly disabled.
198
200
  * @param opts.securityAlertResponse - Response from security validator.
@@ -200,13 +202,16 @@ class TransactionController extends base_controller_1.BaseController {
200
202
  * @param opts.type - Type of transaction to add, such as 'cancel' or 'swap'.
201
203
  * @returns Object containing a promise resolving to the transaction hash if approved.
202
204
  */
203
- addTransaction(txParams, { actionId, deviceConfirmedOn, origin, requireApproval, securityAlertResponse, sendFlowHistory, type, } = {}) {
205
+ addTransaction(txParams, { actionId, deviceConfirmedOn, method, origin, requireApproval, securityAlertResponse, sendFlowHistory, type, } = {}) {
204
206
  return __awaiter(this, void 0, void 0, function* () {
205
207
  const chainId = this.getChainId();
206
208
  const { transactions } = this.state;
207
209
  txParams = (0, utils_1.normalizeTxParams)(txParams);
208
210
  const isEIP1559Compatible = yield this.getEIP1559Compatibility();
209
- (0, utils_1.validateTxParams)(txParams, isEIP1559Compatible);
211
+ (0, validation_1.validateTxParams)(txParams, isEIP1559Compatible);
212
+ if (origin) {
213
+ yield (0, validation_1.validateTransactionOrigin)(yield this.getPermittedAccounts(origin), this.getSelectedAddress(), txParams.from, origin);
214
+ }
210
215
  const dappSuggestedGasFees = this.generateDappSuggestedGasFees(txParams, origin);
211
216
  const transactionType = type !== null && type !== void 0 ? type : (yield (0, transaction_type_1.determineTransactionType)(txParams, this.ethQuery)).type;
212
217
  const existingTransactionMeta = this.getTransactionWithActionId(actionId);
@@ -239,6 +244,11 @@ class TransactionController extends base_controller_1.BaseController {
239
244
  }
240
245
  // Checks if a transaction already exists with a given actionId
241
246
  if (!existingTransactionMeta) {
247
+ // Set security provider response
248
+ if (method && this.securityProviderRequest) {
249
+ const securityProviderResponse = yield this.securityProviderRequest(transactionMeta, method);
250
+ transactionMeta.securityProviderResponse = securityProviderResponse;
251
+ }
242
252
  if (!this.isSendFlowHistoryDisabled) {
243
253
  transactionMeta.sendFlowHistory = sendFlowHistory !== null && sendFlowHistory !== void 0 ? sendFlowHistory : [];
244
254
  }
@@ -495,32 +505,6 @@ class TransactionController extends base_controller_1.BaseController {
495
505
  return { gas: (0, ethereumjs_util_1.addHexPrefix)((0, controller_utils_1.BNToHex)(maxGasBN)), gasPrice, estimateGasError };
496
506
  });
497
507
  }
498
- /**
499
- * Check the status of submitted transactions on the network to determine whether they have
500
- * been included in a block. Any that have been included in a block are marked as confirmed.
501
- */
502
- queryTransactionStatuses() {
503
- return __awaiter(this, void 0, void 0, function* () {
504
- const { transactions } = this.state;
505
- const currentChainId = this.getChainId();
506
- let gotUpdates = false;
507
- yield (0, controller_utils_1.safelyExecute)(() => Promise.all(transactions.map((meta, index) => __awaiter(this, void 0, void 0, function* () {
508
- if (!meta.verifiedOnBlockchain && meta.chainId === currentChainId) {
509
- const [reconciledTx, updateRequired] = yield this.blockchainTransactionStateReconciler(meta);
510
- if (updateRequired) {
511
- transactions[index] = reconciledTx;
512
- gotUpdates = updateRequired;
513
- }
514
- }
515
- }))));
516
- /* istanbul ignore else */
517
- if (gotUpdates) {
518
- this.update({
519
- transactions: this.trimTransactionsForState(transactions),
520
- });
521
- }
522
- });
523
- }
524
508
  /**
525
509
  * Updates an existing transaction in state.
526
510
  *
@@ -530,7 +514,7 @@ class TransactionController extends base_controller_1.BaseController {
530
514
  updateTransaction(transactionMeta, note) {
531
515
  const { transactions } = this.state;
532
516
  transactionMeta.txParams = (0, utils_1.normalizeTxParams)(transactionMeta.txParams);
533
- (0, utils_1.validateTxParams)(transactionMeta.txParams);
517
+ (0, validation_1.validateTxParams)(transactionMeta.txParams);
534
518
  if (!this.isHistoryDisabled) {
535
519
  (0, history_1.updateTransactionHistory)(transactionMeta, note);
536
520
  }
@@ -884,85 +868,6 @@ class TransactionController extends base_controller_1.BaseController {
884
868
  types_1.TransactionStatus.submitted,
885
869
  ].includes(status);
886
870
  }
887
- /**
888
- * Method to verify the state of a transaction using the Blockchain as a source of truth.
889
- *
890
- * @param meta - The local transaction to verify on the blockchain.
891
- * @returns A tuple containing the updated transaction, and whether or not an update was required.
892
- */
893
- blockchainTransactionStateReconciler(meta) {
894
- return __awaiter(this, void 0, void 0, function* () {
895
- const { status, hash } = meta;
896
- switch (status) {
897
- case types_1.TransactionStatus.confirmed:
898
- const txReceipt = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionReceipt', [
899
- hash,
900
- ]);
901
- if (!txReceipt) {
902
- return [meta, false];
903
- }
904
- const txBlock = yield (0, controller_utils_1.query)(this.ethQuery, 'getBlockByHash', [
905
- txReceipt.blockHash,
906
- ]);
907
- meta.verifiedOnBlockchain = true;
908
- meta.txParams.gasUsed = txReceipt.gasUsed;
909
- meta.txReceipt = txReceipt;
910
- meta.baseFeePerGas = txBlock === null || txBlock === void 0 ? void 0 : txBlock.baseFeePerGas;
911
- meta.blockTimestamp = txBlock === null || txBlock === void 0 ? void 0 : txBlock.timestamp;
912
- // According to the Web3 docs:
913
- // TRUE if the transaction was successful, FALSE if the EVM reverted the transaction.
914
- if (Number(txReceipt.status) === 0) {
915
- const error = new Error('Transaction failed. The transaction was reversed');
916
- this.failTransaction(meta, error);
917
- return [meta, false];
918
- }
919
- return [meta, true];
920
- case types_1.TransactionStatus.submitted:
921
- const txObj = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionByHash', [
922
- hash,
923
- ]);
924
- if (!txObj) {
925
- const receiptShowsFailedStatus = yield this.checkTxReceiptStatusIsFailed(hash);
926
- // Case the txObj is evaluated as false, a second check will
927
- // determine if the tx failed or it is pending or confirmed
928
- if (receiptShowsFailedStatus) {
929
- const error = new Error('Transaction failed. The transaction was dropped or replaced by a new one');
930
- this.failTransaction(meta, error);
931
- }
932
- }
933
- /* istanbul ignore next */
934
- if (txObj === null || txObj === void 0 ? void 0 : txObj.blockNumber) {
935
- meta.status = types_1.TransactionStatus.confirmed;
936
- this.hub.emit(`${meta.id}:confirmed`, meta);
937
- return [meta, true];
938
- }
939
- return [meta, false];
940
- default:
941
- return [meta, false];
942
- }
943
- });
944
- }
945
- /**
946
- * Method to check if a tx has failed according to their receipt
947
- * According to the Web3 docs:
948
- * TRUE if the transaction was successful, FALSE if the EVM reverted the transaction.
949
- * The receipt is not available for pending transactions and returns null.
950
- *
951
- * @param txHash - The transaction hash.
952
- * @returns Whether the transaction has failed.
953
- */
954
- checkTxReceiptStatusIsFailed(txHash) {
955
- return __awaiter(this, void 0, void 0, function* () {
956
- const txReceipt = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionReceipt', [
957
- txHash,
958
- ]);
959
- if (!txReceipt) {
960
- // Transaction is pending
961
- return false;
962
- }
963
- return Number(txReceipt.status) === 0;
964
- });
965
- }
966
871
  requestApproval(txMeta, { shouldShowRequest }) {
967
872
  return __awaiter(this, void 0, void 0, function* () {
968
873
  const id = this.getApprovalId(txMeta);
@@ -1043,6 +948,10 @@ class TransactionController extends base_controller_1.BaseController {
1043
948
  this.update({ lastFetchedBlockNumbers });
1044
949
  this.hub.emit('incomingTransactionBlock', blockNumber);
1045
950
  }
951
+ onPendingTransactionsUpdate(transactions) {
952
+ (0, logger_1.pendingTransactionsLogger)('Updated pending transactions');
953
+ this.update({ transactions: this.trimTransactionsForState(transactions) });
954
+ }
1046
955
  generateDappSuggestedGasFees(txParams, origin) {
1047
956
  if (!origin || origin === controller_utils_1.ORIGIN_METAMASK) {
1048
957
  return undefined;