@metamask/transaction-controller 12.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.
@@ -18,9 +18,9 @@ const tx_1 = require("@ethereumjs/tx");
18
18
  const base_controller_1 = require("@metamask/base-controller");
19
19
  const controller_utils_1 = require("@metamask/controller-utils");
20
20
  const eth_query_1 = __importDefault(require("@metamask/eth-query"));
21
+ const rpc_errors_1 = require("@metamask/rpc-errors");
21
22
  const async_mutex_1 = require("async-mutex");
22
23
  const eth_method_registry_1 = __importDefault(require("eth-method-registry"));
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
26
  const lodash_1 = require("lodash");
@@ -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
@@ -57,7 +60,10 @@ class TransactionController extends base_controller_1.BaseController {
57
60
  * @param options.blockTracker - The block tracker used to poll for new blocks data.
58
61
  * @param options.disableHistory - Whether to disable storing history in transaction metadata.
59
62
  * @param options.disableSendFlowHistory - Explicitly disable transaction metadata history.
63
+ * @param options.getCurrentAccountEIP1559Compatibility - Whether or not the account supports EIP-1559.
64
+ * @param options.getCurrentNetworkEIP1559Compatibility - Whether or not the network supports EIP-1559.
60
65
  * @param options.getNetworkState - Gets the state of the network controller.
66
+ * @param options.getPermittedAccounts - Get accounts that a given origin has permissions for.
61
67
  * @param options.getSelectedAddress - Gets the address of the currently selected account.
62
68
  * @param options.incomingTransactions - Configuration options for incoming transaction support.
63
69
  * @param options.incomingTransactions.includeTokenTransfers - Whether or not to include ERC20 token transfers.
@@ -67,10 +73,11 @@ class TransactionController extends base_controller_1.BaseController {
67
73
  * @param options.messenger - The controller messenger.
68
74
  * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.
69
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.
70
77
  * @param config - Initial options used to configure this controller.
71
78
  * @param state - Initial state to set on this controller.
72
79
  */
73
- constructor({ blockTracker, disableHistory, disableSendFlowHistory, 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) {
74
81
  super(config, state);
75
82
  this.mutex = new async_mutex_1.Mutex();
76
83
  /**
@@ -82,7 +89,6 @@ class TransactionController extends base_controller_1.BaseController {
82
89
  */
83
90
  this.name = 'TransactionController';
84
91
  this.defaultConfig = {
85
- interval: 15000,
86
92
  txHistoryLimit: 40,
87
93
  };
88
94
  this.defaultState = {
@@ -98,6 +104,13 @@ class TransactionController extends base_controller_1.BaseController {
98
104
  this.isSendFlowHistoryDisabled = disableSendFlowHistory !== null && disableSendFlowHistory !== void 0 ? disableSendFlowHistory : false;
99
105
  this.isHistoryDisabled = disableHistory !== null && disableHistory !== void 0 ? disableHistory : false;
100
106
  this.registry = new eth_method_registry_1.default({ provider });
107
+ this.getCurrentAccountEIP1559Compatibility =
108
+ getCurrentAccountEIP1559Compatibility;
109
+ this.getCurrentNetworkEIP1559Compatibility =
110
+ getCurrentNetworkEIP1559Compatibility;
111
+ this.getPermittedAccounts = getPermittedAccounts;
112
+ this.getSelectedAddress = getSelectedAddress;
113
+ this.securityProviderRequest = securityProviderRequest;
101
114
  this.nonceTracker = new nonce_tracker_1.default({
102
115
  provider,
103
116
  blockTracker,
@@ -119,11 +132,20 @@ class TransactionController extends base_controller_1.BaseController {
119
132
  });
120
133
  this.incomingTransactionHelper.hub.on('transactions', this.onIncomingTransactions.bind(this));
121
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));
122
144
  onNetworkStateChange(() => {
123
145
  this.ethQuery = new eth_query_1.default(this.provider);
124
146
  this.registry = new eth_method_registry_1.default({ provider: this.provider });
125
147
  });
126
- this.poll();
148
+ this.pendingTransactionTracker.start();
127
149
  }
128
150
  failTransaction(transactionMeta, error) {
129
151
  const newTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { error, status: types_1.TransactionStatus.failed });
@@ -137,21 +159,6 @@ class TransactionController extends base_controller_1.BaseController {
137
159
  return { registryMethod, parsedRegistryMethod };
138
160
  });
139
161
  }
140
- /**
141
- * Starts a new polling interval.
142
- *
143
- * @param interval - The polling interval used to fetch new transaction statuses.
144
- */
145
- poll(interval) {
146
- return __awaiter(this, void 0, void 0, function* () {
147
- interval && this.configure({ interval }, false, false);
148
- this.handle && clearTimeout(this.handle);
149
- yield (0, controller_utils_1.safelyExecute)(() => this.queryTransactionStatuses());
150
- this.handle = setTimeout(() => {
151
- this.poll(this.config.interval);
152
- }, this.config.interval);
153
- });
154
- }
155
162
  /**
156
163
  * Handle new method data request.
157
164
  *
@@ -187,6 +194,7 @@ class TransactionController extends base_controller_1.BaseController {
187
194
  * @param opts - Additional options to control how the transaction is added.
188
195
  * @param opts.actionId - Unique ID to prevent duplicate requests.
189
196
  * @param opts.deviceConfirmedOn - An enum to indicate what device confirmed the transaction.
197
+ * @param opts.method - RPC method that requested the transaction.
190
198
  * @param opts.origin - The origin of the transaction request, such as a dApp hostname.
191
199
  * @param opts.requireApproval - Whether the transaction requires approval by the user, defaults to true unless explicitly disabled.
192
200
  * @param opts.securityAlertResponse - Response from security validator.
@@ -194,12 +202,16 @@ class TransactionController extends base_controller_1.BaseController {
194
202
  * @param opts.type - Type of transaction to add, such as 'cancel' or 'swap'.
195
203
  * @returns Object containing a promise resolving to the transaction hash if approved.
196
204
  */
197
- addTransaction(txParams, { actionId, deviceConfirmedOn, origin, requireApproval, securityAlertResponse, sendFlowHistory, type, } = {}) {
205
+ addTransaction(txParams, { actionId, deviceConfirmedOn, method, origin, requireApproval, securityAlertResponse, sendFlowHistory, type, } = {}) {
198
206
  return __awaiter(this, void 0, void 0, function* () {
199
207
  const chainId = this.getChainId();
200
208
  const { transactions } = this.state;
201
209
  txParams = (0, utils_1.normalizeTxParams)(txParams);
202
- (0, utils_1.validateTxParams)(txParams);
210
+ const isEIP1559Compatible = yield this.getEIP1559Compatibility();
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
+ }
203
215
  const dappSuggestedGasFees = this.generateDappSuggestedGasFees(txParams, origin);
204
216
  const transactionType = type !== null && type !== void 0 ? type : (yield (0, transaction_type_1.determineTransactionType)(txParams, this.ethQuery)).type;
205
217
  const existingTransactionMeta = this.getTransactionWithActionId(actionId);
@@ -232,6 +244,11 @@ class TransactionController extends base_controller_1.BaseController {
232
244
  }
233
245
  // Checks if a transaction already exists with a given actionId
234
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
+ }
235
252
  if (!this.isSendFlowHistoryDisabled) {
236
253
  transactionMeta.sendFlowHistory = sendFlowHistory !== null && sendFlowHistory !== void 0 ? sendFlowHistory : [];
237
254
  }
@@ -488,32 +505,6 @@ class TransactionController extends base_controller_1.BaseController {
488
505
  return { gas: (0, ethereumjs_util_1.addHexPrefix)((0, controller_utils_1.BNToHex)(maxGasBN)), gasPrice, estimateGasError };
489
506
  });
490
507
  }
491
- /**
492
- * Check the status of submitted transactions on the network to determine whether they have
493
- * been included in a block. Any that have been included in a block are marked as confirmed.
494
- */
495
- queryTransactionStatuses() {
496
- return __awaiter(this, void 0, void 0, function* () {
497
- const { transactions } = this.state;
498
- const currentChainId = this.getChainId();
499
- let gotUpdates = false;
500
- yield (0, controller_utils_1.safelyExecute)(() => Promise.all(transactions.map((meta, index) => __awaiter(this, void 0, void 0, function* () {
501
- if (!meta.verifiedOnBlockchain && meta.chainId === currentChainId) {
502
- const [reconciledTx, updateRequired] = yield this.blockchainTransactionStateReconciler(meta);
503
- if (updateRequired) {
504
- transactions[index] = reconciledTx;
505
- gotUpdates = updateRequired;
506
- }
507
- }
508
- }))));
509
- /* istanbul ignore else */
510
- if (gotUpdates) {
511
- this.update({
512
- transactions: this.trimTransactionsForState(transactions),
513
- });
514
- }
515
- });
516
- }
517
508
  /**
518
509
  * Updates an existing transaction in state.
519
510
  *
@@ -523,7 +514,7 @@ class TransactionController extends base_controller_1.BaseController {
523
514
  updateTransaction(transactionMeta, note) {
524
515
  const { transactions } = this.state;
525
516
  transactionMeta.txParams = (0, utils_1.normalizeTxParams)(transactionMeta.txParams);
526
- (0, utils_1.validateTxParams)(transactionMeta.txParams);
517
+ (0, validation_1.validateTxParams)(transactionMeta.txParams);
527
518
  if (!this.isHistoryDisabled) {
528
519
  (0, history_1.updateTransactionHistory)(transactionMeta, note);
529
520
  }
@@ -693,9 +684,9 @@ class TransactionController extends base_controller_1.BaseController {
693
684
  catch (error) {
694
685
  const { isCompleted: isTxCompleted } = this.isTransactionCompleted(transactionId);
695
686
  if (!isTxCompleted) {
696
- if (error.code === eth_rpc_errors_1.errorCodes.provider.userRejectedRequest) {
687
+ if (error.code === rpc_errors_1.errorCodes.provider.userRejectedRequest) {
697
688
  this.cancelTransaction(transactionId);
698
- throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the transaction');
689
+ throw rpc_errors_1.providerErrors.userRejectedRequest('User rejected the transaction');
699
690
  }
700
691
  else {
701
692
  this.failTransaction(meta, error);
@@ -707,16 +698,16 @@ class TransactionController extends base_controller_1.BaseController {
707
698
  switch (finalMeta === null || finalMeta === void 0 ? void 0 : finalMeta.status) {
708
699
  case types_1.TransactionStatus.failed:
709
700
  resultCallbacks === null || resultCallbacks === void 0 ? void 0 : resultCallbacks.error(finalMeta.error);
710
- throw eth_rpc_errors_1.ethErrors.rpc.internal(finalMeta.error.message);
701
+ throw rpc_errors_1.rpcErrors.internal(finalMeta.error.message);
711
702
  case types_1.TransactionStatus.cancelled:
712
- const cancelError = eth_rpc_errors_1.ethErrors.rpc.internal('User cancelled the transaction');
703
+ const cancelError = rpc_errors_1.rpcErrors.internal('User cancelled the transaction');
713
704
  resultCallbacks === null || resultCallbacks === void 0 ? void 0 : resultCallbacks.error(cancelError);
714
705
  throw cancelError;
715
706
  case types_1.TransactionStatus.submitted:
716
707
  resultCallbacks === null || resultCallbacks === void 0 ? void 0 : resultCallbacks.success();
717
708
  return finalMeta.hash;
718
709
  default:
719
- const internalError = eth_rpc_errors_1.ethErrors.rpc.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finalMeta || transactionId)}`);
710
+ const internalError = rpc_errors_1.rpcErrors.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finalMeta || transactionId)}`);
720
711
  resultCallbacks === null || resultCallbacks === void 0 ? void 0 : resultCallbacks.error(internalError);
721
712
  throw internalError;
722
713
  }
@@ -877,85 +868,6 @@ class TransactionController extends base_controller_1.BaseController {
877
868
  types_1.TransactionStatus.submitted,
878
869
  ].includes(status);
879
870
  }
880
- /**
881
- * Method to verify the state of a transaction using the Blockchain as a source of truth.
882
- *
883
- * @param meta - The local transaction to verify on the blockchain.
884
- * @returns A tuple containing the updated transaction, and whether or not an update was required.
885
- */
886
- blockchainTransactionStateReconciler(meta) {
887
- return __awaiter(this, void 0, void 0, function* () {
888
- const { status, hash } = meta;
889
- switch (status) {
890
- case types_1.TransactionStatus.confirmed:
891
- const txReceipt = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionReceipt', [
892
- hash,
893
- ]);
894
- if (!txReceipt) {
895
- return [meta, false];
896
- }
897
- const txBlock = yield (0, controller_utils_1.query)(this.ethQuery, 'getBlockByHash', [
898
- txReceipt.blockHash,
899
- ]);
900
- meta.verifiedOnBlockchain = true;
901
- meta.txParams.gasUsed = txReceipt.gasUsed;
902
- meta.txReceipt = txReceipt;
903
- meta.baseFeePerGas = txBlock === null || txBlock === void 0 ? void 0 : txBlock.baseFeePerGas;
904
- meta.blockTimestamp = txBlock === null || txBlock === void 0 ? void 0 : txBlock.timestamp;
905
- // According to the Web3 docs:
906
- // TRUE if the transaction was successful, FALSE if the EVM reverted the transaction.
907
- if (Number(txReceipt.status) === 0) {
908
- const error = new Error('Transaction failed. The transaction was reversed');
909
- this.failTransaction(meta, error);
910
- return [meta, false];
911
- }
912
- return [meta, true];
913
- case types_1.TransactionStatus.submitted:
914
- const txObj = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionByHash', [
915
- hash,
916
- ]);
917
- if (!txObj) {
918
- const receiptShowsFailedStatus = yield this.checkTxReceiptStatusIsFailed(hash);
919
- // Case the txObj is evaluated as false, a second check will
920
- // determine if the tx failed or it is pending or confirmed
921
- if (receiptShowsFailedStatus) {
922
- const error = new Error('Transaction failed. The transaction was dropped or replaced by a new one');
923
- this.failTransaction(meta, error);
924
- }
925
- }
926
- /* istanbul ignore next */
927
- if (txObj === null || txObj === void 0 ? void 0 : txObj.blockNumber) {
928
- meta.status = types_1.TransactionStatus.confirmed;
929
- this.hub.emit(`${meta.id}:confirmed`, meta);
930
- return [meta, true];
931
- }
932
- return [meta, false];
933
- default:
934
- return [meta, false];
935
- }
936
- });
937
- }
938
- /**
939
- * Method to check if a tx has failed according to their receipt
940
- * According to the Web3 docs:
941
- * TRUE if the transaction was successful, FALSE if the EVM reverted the transaction.
942
- * The receipt is not available for pending transactions and returns null.
943
- *
944
- * @param txHash - The transaction hash.
945
- * @returns Whether the transaction has failed.
946
- */
947
- checkTxReceiptStatusIsFailed(txHash) {
948
- return __awaiter(this, void 0, void 0, function* () {
949
- const txReceipt = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionReceipt', [
950
- txHash,
951
- ]);
952
- if (!txReceipt) {
953
- // Transaction is pending
954
- return false;
955
- }
956
- return Number(txReceipt.status) === 0;
957
- });
958
- }
959
871
  requestApproval(txMeta, { shouldShowRequest }) {
960
872
  return __awaiter(this, void 0, void 0, function* () {
961
873
  const id = this.getApprovalId(txMeta);
@@ -1036,6 +948,10 @@ class TransactionController extends base_controller_1.BaseController {
1036
948
  this.update({ lastFetchedBlockNumbers });
1037
949
  this.hub.emit('incomingTransactionBlock', blockNumber);
1038
950
  }
951
+ onPendingTransactionsUpdate(transactions) {
952
+ (0, logger_1.pendingTransactionsLogger)('Updated pending transactions');
953
+ this.update({ transactions: this.trimTransactionsForState(transactions) });
954
+ }
1039
955
  generateDappSuggestedGasFees(txParams, origin) {
1040
956
  if (!origin || origin === controller_utils_1.ORIGIN_METAMASK) {
1041
957
  return undefined;
@@ -1167,6 +1083,14 @@ class TransactionController extends base_controller_1.BaseController {
1167
1083
  }
1168
1084
  });
1169
1085
  }
1086
+ getEIP1559Compatibility() {
1087
+ var _a, _b;
1088
+ return __awaiter(this, void 0, void 0, function* () {
1089
+ const currentNetworkIsEIP1559Compatible = yield this.getCurrentNetworkEIP1559Compatibility();
1090
+ const currentAccountIsEIP1559Compatible = (_b = (_a = this.getCurrentAccountEIP1559Compatibility) === null || _a === void 0 ? void 0 : _a.call(this)) !== null && _b !== void 0 ? _b : true;
1091
+ return (currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible);
1092
+ });
1093
+ }
1170
1094
  }
1171
1095
  exports.TransactionController = TransactionController;
1172
1096
  exports.default = TransactionController;