@metamask/transaction-controller 13.0.0 → 15.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 = {
@@ -96,6 +100,7 @@ class TransactionController extends base_controller_1.BaseController {
96
100
  this.provider = provider;
97
101
  this.messagingSystem = messenger;
98
102
  this.getNetworkState = getNetworkState;
103
+ // @ts-expect-error TODO: Provider type alignment
99
104
  this.ethQuery = new eth_query_1.default(provider);
100
105
  this.isSendFlowHistoryDisabled = disableSendFlowHistory !== null && disableSendFlowHistory !== void 0 ? disableSendFlowHistory : false;
101
106
  this.isHistoryDisabled = disableHistory !== null && disableHistory !== void 0 ? disableHistory : false;
@@ -104,6 +109,9 @@ class TransactionController extends base_controller_1.BaseController {
104
109
  getCurrentAccountEIP1559Compatibility;
105
110
  this.getCurrentNetworkEIP1559Compatibility =
106
111
  getCurrentNetworkEIP1559Compatibility;
112
+ this.getPermittedAccounts = getPermittedAccounts;
113
+ this.getSelectedAddress = getSelectedAddress;
114
+ this.securityProviderRequest = securityProviderRequest;
107
115
  this.nonceTracker = new nonce_tracker_1.default({
108
116
  provider,
109
117
  blockTracker,
@@ -125,11 +133,21 @@ class TransactionController extends base_controller_1.BaseController {
125
133
  });
126
134
  this.incomingTransactionHelper.hub.on('transactions', this.onIncomingTransactions.bind(this));
127
135
  this.incomingTransactionHelper.hub.on('updatedLastFetchedBlockNumbers', this.onUpdatedLastFetchedBlockNumbers.bind(this));
136
+ this.pendingTransactionTracker = new PendingTransactionTracker_1.PendingTransactionTracker({
137
+ blockTracker,
138
+ failTransaction: this.failTransaction.bind(this),
139
+ getChainId: this.getChainId.bind(this),
140
+ getEthQuery: () => this.ethQuery,
141
+ getTransactions: () => this.state.transactions,
142
+ });
143
+ this.pendingTransactionTracker.hub.on('transactions', this.onPendingTransactionsUpdate.bind(this));
144
+ this.pendingTransactionTracker.hub.on('transaction-confirmed', (transactionMeta) => this.hub.emit(`${transactionMeta.id}:confirmed`, transactionMeta));
128
145
  onNetworkStateChange(() => {
146
+ // @ts-expect-error TODO: Provider type alignment
129
147
  this.ethQuery = new eth_query_1.default(this.provider);
130
148
  this.registry = new eth_method_registry_1.default({ provider: this.provider });
131
149
  });
132
- this.poll();
150
+ this.pendingTransactionTracker.start();
133
151
  }
134
152
  failTransaction(transactionMeta, error) {
135
153
  const newTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { error, status: types_1.TransactionStatus.failed });
@@ -143,21 +161,6 @@ class TransactionController extends base_controller_1.BaseController {
143
161
  return { registryMethod, parsedRegistryMethod };
144
162
  });
145
163
  }
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
164
  /**
162
165
  * Handle new method data request.
163
166
  *
@@ -193,6 +196,7 @@ class TransactionController extends base_controller_1.BaseController {
193
196
  * @param opts - Additional options to control how the transaction is added.
194
197
  * @param opts.actionId - Unique ID to prevent duplicate requests.
195
198
  * @param opts.deviceConfirmedOn - An enum to indicate what device confirmed the transaction.
199
+ * @param opts.method - RPC method that requested the transaction.
196
200
  * @param opts.origin - The origin of the transaction request, such as a dApp hostname.
197
201
  * @param opts.requireApproval - Whether the transaction requires approval by the user, defaults to true unless explicitly disabled.
198
202
  * @param opts.securityAlertResponse - Response from security validator.
@@ -200,13 +204,16 @@ class TransactionController extends base_controller_1.BaseController {
200
204
  * @param opts.type - Type of transaction to add, such as 'cancel' or 'swap'.
201
205
  * @returns Object containing a promise resolving to the transaction hash if approved.
202
206
  */
203
- addTransaction(txParams, { actionId, deviceConfirmedOn, origin, requireApproval, securityAlertResponse, sendFlowHistory, type, } = {}) {
207
+ addTransaction(txParams, { actionId, deviceConfirmedOn, method, origin, requireApproval, securityAlertResponse, sendFlowHistory, type, } = {}) {
204
208
  return __awaiter(this, void 0, void 0, function* () {
205
209
  const chainId = this.getChainId();
206
210
  const { transactions } = this.state;
207
211
  txParams = (0, utils_1.normalizeTxParams)(txParams);
208
212
  const isEIP1559Compatible = yield this.getEIP1559Compatibility();
209
- (0, utils_1.validateTxParams)(txParams, isEIP1559Compatible);
213
+ (0, validation_1.validateTxParams)(txParams, isEIP1559Compatible);
214
+ if (origin) {
215
+ yield (0, validation_1.validateTransactionOrigin)(yield this.getPermittedAccounts(origin), this.getSelectedAddress(), txParams.from, origin);
216
+ }
210
217
  const dappSuggestedGasFees = this.generateDappSuggestedGasFees(txParams, origin);
211
218
  const transactionType = type !== null && type !== void 0 ? type : (yield (0, transaction_type_1.determineTransactionType)(txParams, this.ethQuery)).type;
212
219
  const existingTransactionMeta = this.getTransactionWithActionId(actionId);
@@ -239,6 +246,11 @@ class TransactionController extends base_controller_1.BaseController {
239
246
  }
240
247
  // Checks if a transaction already exists with a given actionId
241
248
  if (!existingTransactionMeta) {
249
+ // Set security provider response
250
+ if (method && this.securityProviderRequest) {
251
+ const securityProviderResponse = yield this.securityProviderRequest(transactionMeta, method);
252
+ transactionMeta.securityProviderResponse = securityProviderResponse;
253
+ }
242
254
  if (!this.isSendFlowHistoryDisabled) {
243
255
  transactionMeta.sendFlowHistory = sendFlowHistory !== null && sendFlowHistory !== void 0 ? sendFlowHistory : [];
244
256
  }
@@ -495,32 +507,6 @@ class TransactionController extends base_controller_1.BaseController {
495
507
  return { gas: (0, ethereumjs_util_1.addHexPrefix)((0, controller_utils_1.BNToHex)(maxGasBN)), gasPrice, estimateGasError };
496
508
  });
497
509
  }
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
510
  /**
525
511
  * Updates an existing transaction in state.
526
512
  *
@@ -530,7 +516,7 @@ class TransactionController extends base_controller_1.BaseController {
530
516
  updateTransaction(transactionMeta, note) {
531
517
  const { transactions } = this.state;
532
518
  transactionMeta.txParams = (0, utils_1.normalizeTxParams)(transactionMeta.txParams);
533
- (0, utils_1.validateTxParams)(transactionMeta.txParams);
519
+ (0, validation_1.validateTxParams)(transactionMeta.txParams);
534
520
  if (!this.isHistoryDisabled) {
535
521
  (0, history_1.updateTransactionHistory)(transactionMeta, note);
536
522
  }
@@ -884,85 +870,6 @@ class TransactionController extends base_controller_1.BaseController {
884
870
  types_1.TransactionStatus.submitted,
885
871
  ].includes(status);
886
872
  }
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
873
  requestApproval(txMeta, { shouldShowRequest }) {
967
874
  return __awaiter(this, void 0, void 0, function* () {
968
875
  const id = this.getApprovalId(txMeta);
@@ -1043,6 +950,10 @@ class TransactionController extends base_controller_1.BaseController {
1043
950
  this.update({ lastFetchedBlockNumbers });
1044
951
  this.hub.emit('incomingTransactionBlock', blockNumber);
1045
952
  }
953
+ onPendingTransactionsUpdate(transactions) {
954
+ (0, logger_1.pendingTransactionsLogger)('Updated pending transactions');
955
+ this.update({ transactions: this.trimTransactionsForState(transactions) });
956
+ }
1046
957
  generateDappSuggestedGasFees(txParams, origin) {
1047
958
  if (!origin || origin === controller_utils_1.ORIGIN_METAMASK) {
1048
959
  return undefined;