@toruslabs/ethereum-controllers 5.10.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/ethereumControllers.cjs.js +114 -432
  2. package/dist/ethereumControllers.esm.js +63 -351
  3. package/dist/ethereumControllers.umd.min.js +1 -1
  4. package/dist/ethereumControllers.umd.min.js.LICENSE.txt +7 -2
  5. package/dist/lib.cjs/Account/AccountTrackerController.js +160 -0
  6. package/dist/lib.cjs/Block/PollingBlockTracker.js +85 -0
  7. package/dist/lib.cjs/Currency/CurrencyController.js +111 -0
  8. package/dist/lib.cjs/Gas/GasFeeController.js +214 -0
  9. package/dist/lib.cjs/Gas/gasUtil.js +148 -0
  10. package/dist/lib.cjs/Keyring/KeyringController.js +93 -0
  11. package/dist/lib.cjs/Message/AbstractMessageController.js +107 -0
  12. package/dist/lib.cjs/Message/AddChainController.js +78 -0
  13. package/dist/lib.cjs/Message/MessageController.js +77 -0
  14. package/dist/lib.cjs/Message/PersonalMessageController.js +77 -0
  15. package/dist/lib.cjs/Message/SwitchChainController.js +78 -0
  16. package/dist/lib.cjs/Message/TypedMessageController.js +81 -0
  17. package/dist/lib.cjs/Message/utils.js +112 -0
  18. package/dist/lib.cjs/Network/NetworkController.js +201 -0
  19. package/dist/lib.cjs/Network/cacheIdentifier.js +112 -0
  20. package/dist/lib.cjs/Network/createEthereumMiddleware.js +302 -0
  21. package/dist/lib.cjs/Network/createJsonRpcClient.js +64 -0
  22. package/dist/lib.cjs/Nfts/NftHandler.js +180 -0
  23. package/dist/lib.cjs/Nfts/NftsController.js +213 -0
  24. package/dist/lib.cjs/Preferences/PreferencesController.js +476 -0
  25. package/dist/lib.cjs/Tokens/TokenHandler.js +51 -0
  26. package/dist/lib.cjs/Tokens/TokenRatesController.js +112 -0
  27. package/dist/lib.cjs/Tokens/TokensController.js +259 -0
  28. package/dist/lib.cjs/Transaction/NonceTracker.js +150 -0
  29. package/dist/lib.cjs/Transaction/PendingTransactionTracker.js +222 -0
  30. package/dist/lib.cjs/Transaction/TransactionController.js +515 -0
  31. package/dist/lib.cjs/Transaction/TransactionGasUtil.js +81 -0
  32. package/dist/lib.cjs/Transaction/TransactionStateHistoryHelper.js +42 -0
  33. package/dist/lib.cjs/Transaction/TransactionStateManager.js +296 -0
  34. package/dist/lib.cjs/Transaction/TransactionUtils.js +341 -0
  35. package/dist/lib.cjs/index.js +171 -0
  36. package/dist/lib.cjs/utils/abis.js +510 -0
  37. package/dist/lib.cjs/utils/constants.js +362 -0
  38. package/dist/lib.cjs/utils/contractAddresses.js +16 -0
  39. package/dist/lib.cjs/utils/conversionUtils.js +232 -0
  40. package/dist/lib.cjs/utils/helpers.js +244 -0
  41. package/dist/lib.cjs/utils/lodashUtils.js +25 -0
  42. package/dist/lib.esm/Account/AccountTrackerController.js +158 -0
  43. package/dist/lib.esm/Block/PollingBlockTracker.js +83 -0
  44. package/dist/lib.esm/Currency/CurrencyController.js +109 -0
  45. package/dist/lib.esm/Gas/GasFeeController.js +212 -0
  46. package/dist/lib.esm/Gas/gasUtil.js +141 -0
  47. package/dist/lib.esm/Keyring/KeyringController.js +91 -0
  48. package/dist/lib.esm/Message/AbstractMessageController.js +105 -0
  49. package/dist/lib.esm/Message/AddChainController.js +76 -0
  50. package/dist/lib.esm/Message/MessageController.js +75 -0
  51. package/dist/lib.esm/Message/PersonalMessageController.js +75 -0
  52. package/dist/lib.esm/Message/SwitchChainController.js +76 -0
  53. package/dist/lib.esm/Message/TypedMessageController.js +79 -0
  54. package/dist/lib.esm/Message/utils.js +105 -0
  55. package/dist/lib.esm/Network/NetworkController.js +199 -0
  56. package/dist/lib.esm/Network/cacheIdentifier.js +107 -0
  57. package/dist/lib.esm/Network/createEthereumMiddleware.js +289 -0
  58. package/dist/lib.esm/Network/createJsonRpcClient.js +60 -0
  59. package/dist/lib.esm/Nfts/NftHandler.js +178 -0
  60. package/dist/lib.esm/Nfts/NftsController.js +211 -0
  61. package/dist/lib.esm/Preferences/PreferencesController.js +474 -0
  62. package/dist/lib.esm/Tokens/TokenHandler.js +49 -0
  63. package/dist/lib.esm/Tokens/TokenRatesController.js +109 -0
  64. package/dist/lib.esm/Tokens/TokensController.js +257 -0
  65. package/dist/lib.esm/Transaction/NonceTracker.js +148 -0
  66. package/dist/lib.esm/Transaction/PendingTransactionTracker.js +220 -0
  67. package/dist/lib.esm/Transaction/TransactionController.js +513 -0
  68. package/dist/lib.esm/Transaction/TransactionGasUtil.js +79 -0
  69. package/dist/lib.esm/Transaction/TransactionStateHistoryHelper.js +38 -0
  70. package/dist/lib.esm/Transaction/TransactionStateManager.js +294 -0
  71. package/dist/lib.esm/Transaction/TransactionUtils.js +326 -0
  72. package/dist/lib.esm/index.js +33 -0
  73. package/dist/lib.esm/utils/abis.js +505 -0
  74. package/dist/lib.esm/utils/constants.js +323 -0
  75. package/dist/lib.esm/utils/contractAddresses.js +14 -0
  76. package/dist/lib.esm/utils/conversionUtils.js +218 -0
  77. package/dist/lib.esm/utils/helpers.js +227 -0
  78. package/dist/lib.esm/utils/lodashUtils.js +21 -0
  79. package/dist/types/Account/AccountTrackerController.d.ts +5 -5
  80. package/dist/types/Block/PollingBlockTracker.d.ts +1 -2
  81. package/dist/types/Currency/CurrencyController.d.ts +1 -1
  82. package/dist/types/Gas/GasFeeController.d.ts +3 -3
  83. package/dist/types/Gas/gasUtil.d.ts +1 -1
  84. package/dist/types/Keyring/KeyringController.d.ts +3 -5
  85. package/dist/types/Message/AbstractMessageController.d.ts +5 -6
  86. package/dist/types/Message/AddChainController.d.ts +4 -4
  87. package/dist/types/Message/MessageController.d.ts +4 -4
  88. package/dist/types/Message/PersonalMessageController.d.ts +4 -4
  89. package/dist/types/Message/SwitchChainController.d.ts +4 -4
  90. package/dist/types/Message/TypedMessageController.d.ts +6 -7
  91. package/dist/types/Message/utils.d.ts +2 -7
  92. package/dist/types/Network/NetworkController.d.ts +4 -4
  93. package/dist/types/Network/cacheIdentifier.d.ts +1 -1
  94. package/dist/types/Network/createEthereumMiddleware.d.ts +2 -18
  95. package/dist/types/Network/createJsonRpcClient.d.ts +2 -2
  96. package/dist/types/Nfts/NftsController.d.ts +2 -2
  97. package/dist/types/Preferences/PreferencesController.d.ts +4 -4
  98. package/dist/types/Tokens/TokensController.d.ts +3 -3
  99. package/dist/types/Transaction/NonceTracker.d.ts +5 -5
  100. package/dist/types/Transaction/PendingTransactionTracker.d.ts +5 -5
  101. package/dist/types/Transaction/TransactionController.d.ts +12 -12
  102. package/dist/types/Transaction/TransactionGasUtil.d.ts +4 -4
  103. package/dist/types/Transaction/TransactionStateManager.d.ts +3 -3
  104. package/dist/types/Transaction/TransactionUtils.d.ts +1 -1
  105. package/dist/types/index.d.ts +12 -14
  106. package/dist/types/utils/constants.d.ts +1 -5
  107. package/dist/types/utils/helpers.d.ts +7 -4
  108. package/dist/types/utils/interfaces.d.ts +43 -23
  109. package/package.json +22 -10
  110. package/dist/types/Message/DecryptMessageController.d.ts +0 -20
  111. package/dist/types/Message/EncryptionPublicKeyController.d.ts +0 -20
@@ -0,0 +1,513 @@
1
+ import _objectSpread from '@babel/runtime/helpers/objectSpread2';
2
+ import _defineProperty from '@babel/runtime/helpers/defineProperty';
3
+ import { addHexPrefix, stripHexPrefix } from '@ethereumjs/util';
4
+ import { TRANSACTION_TYPES, TX_EVENTS, TransactionStatus } from '@toruslabs/base-controllers';
5
+ import { providerErrors, rpcErrors } from '@web3auth/auth';
6
+ import BigNumber from 'bignumber.js';
7
+ import { keccak256 } from 'ethers';
8
+ import log from 'loglevel';
9
+ import { METHOD_TYPES, TRANSACTION_ENVELOPE_TYPES, GAS_ESTIMATE_TYPES, CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP } from '../utils/constants.js';
10
+ import { decGWEIToHexWEI } from '../utils/conversionUtils.js';
11
+ import { bnLessThan, getChainType, GAS_LIMITS } from '../utils/helpers.js';
12
+ import { NonceTracker } from './NonceTracker.js';
13
+ import { PendingTransactionTracker } from './PendingTransactionTracker.js';
14
+ import { TransactionGasUtil } from './TransactionGasUtil.js';
15
+ import { TransactionStateManager } from './TransactionStateManager.js';
16
+ import { isEIP1559Transaction, normalizeTxParameters, validateTxParameters, determineTransactionType } from './TransactionUtils.js';
17
+
18
+ class TransactionController extends TransactionStateManager {
19
+ constructor({
20
+ config,
21
+ state,
22
+ provider,
23
+ blockTracker,
24
+ signEthTx,
25
+ getCurrentChainId,
26
+ getCurrentNetworkEIP1559Compatibility,
27
+ getProviderConfig,
28
+ getCurrentAccountEIP1559Compatibility,
29
+ getSelectedAddress,
30
+ getEIP1559GasFeeEstimates
31
+ }) {
32
+ super({
33
+ config,
34
+ state,
35
+ getCurrentChainId
36
+ });
37
+ _defineProperty(this, "getSelectedAddress", void 0);
38
+ _defineProperty(this, "getEIP1559GasFeeEstimates", void 0);
39
+ _defineProperty(this, "nonceTracker", void 0);
40
+ _defineProperty(this, "pendingTxTracker", void 0);
41
+ _defineProperty(this, "txGasUtil", void 0);
42
+ _defineProperty(this, "_getCurrentNetworkEIP1559Compatibility", void 0);
43
+ _defineProperty(this, "_getCurrentAccountEIP1559Compatibility", void 0);
44
+ _defineProperty(this, "getProviderConfig", void 0);
45
+ _defineProperty(this, "signEthTx", void 0);
46
+ _defineProperty(this, "provider", void 0);
47
+ _defineProperty(this, "blockTracker", void 0);
48
+ _defineProperty(this, "inProcessOfSigning", new Set());
49
+ _defineProperty(this, "getUnapprovedTxCount", () => Object.keys(this.getUnapprovedTxList()).length);
50
+ _defineProperty(this, "getPendingTxCount", account => this.getPendingTransactions(account).length);
51
+ this.blockTracker = blockTracker;
52
+ this.getProviderConfig = getProviderConfig;
53
+ this._getCurrentNetworkEIP1559Compatibility = getCurrentNetworkEIP1559Compatibility;
54
+ this._getCurrentAccountEIP1559Compatibility = getCurrentAccountEIP1559Compatibility;
55
+ this.getSelectedAddress = getSelectedAddress;
56
+ this.getEIP1559GasFeeEstimates = getEIP1559GasFeeEstimates;
57
+ this.signEthTx = signEthTx;
58
+ this.provider = provider;
59
+ this.txGasUtil = new TransactionGasUtil(this.provider, this.blockTracker);
60
+ this.nonceTracker = new NonceTracker({
61
+ provider,
62
+ blockTracker,
63
+ getConfirmedTransactions: this.getConfirmedTransactions.bind(this),
64
+ getPendingTransactions: this.getSubmittedTransactions.bind(this) // nonce tracker should only care about submitted transactions
65
+ });
66
+ this.pendingTxTracker = new PendingTransactionTracker({
67
+ provider,
68
+ nonceTracker: this.nonceTracker,
69
+ getPendingTransactions: this.getPendingTransactions.bind(this),
70
+ // pending tx tracker should only care about submitted and approved transactions
71
+ getConfirmedTransactions: this.getConfirmedTransactions.bind(this),
72
+ approveTransaction: this.approveTransaction.bind(this),
73
+ publishTransaction: rawTx => this.provider.request({
74
+ method: METHOD_TYPES.ETH_SEND_RAW_TRANSACTION,
75
+ params: [rawTx]
76
+ })
77
+ });
78
+ this._setupListeners();
79
+ }
80
+ addTransactionUnapproved(txMeta) {
81
+ this.addTransactionToState(txMeta);
82
+ this.emit(`${txMeta.id}:unapproved`, txMeta);
83
+ }
84
+ async addNewUnapprovedTransaction(txParams, req) {
85
+ const txMeta = await this.createTransaction(txParams, req);
86
+ return this.processApproval(txMeta);
87
+ }
88
+ async processApproval(txMeta) {
89
+ return new Promise((resolve, reject) => {
90
+ const handleFinished = msg => {
91
+ if (msg.status === TransactionStatus.rejected) {
92
+ return reject(providerErrors.userRejectedRequest(`Transaction Signature: User denied message signature`));
93
+ }
94
+ if (msg.status === TransactionStatus.failed) {
95
+ return reject(rpcErrors.internal(`Transaction Signature: failed to sign message ${msg.error}`));
96
+ }
97
+ if (msg.status === TransactionStatus.submitted) {
98
+ return resolve(msg.transactionHash);
99
+ }
100
+ return reject(rpcErrors.internal(`Transaction Signature: Unknown problem: ${JSON.stringify(txMeta.transaction)}`));
101
+ };
102
+ this.once(`${txMeta.id}:finished`, handleFinished);
103
+ });
104
+ }
105
+ async approveTransaction(transactionID) {
106
+ const txMeta = this.getTransaction(transactionID);
107
+ if (this.inProcessOfSigning.has(transactionID)) {
108
+ return;
109
+ }
110
+ this.inProcessOfSigning.add(transactionID);
111
+ let nonceLock;
112
+ try {
113
+ this.setTxStatusApproved(transactionID);
114
+ const fromAddress = txMeta.transaction.from;
115
+ const {
116
+ customNonceValue
117
+ } = txMeta.transaction;
118
+ const customNonceValueNumber = Number(customNonceValue);
119
+ nonceLock = await this.nonceTracker.getNonceLock(fromAddress);
120
+ // add nonce to txParams
121
+ // if txMeta has previousGasParams then it is a retry at same nonce with
122
+ // higher gas settings and therefor the nonce should not be recalculated
123
+ const nonce = nonceLock.nextNonce;
124
+ const customOrNonce = customNonceValueNumber === 0 ? customNonceValue : customNonceValue || nonce;
125
+ txMeta.transaction.nonce = addHexPrefix(customOrNonce.toString(16));
126
+ // add nonce debugging information to txMeta
127
+ txMeta.nonceDetails = nonceLock.nonceDetails;
128
+ this.updateTransactionInState(txMeta, "transactions#approveTransaction");
129
+ // sign transaction
130
+ const rawTx = await this.signTransaction(transactionID);
131
+ await this.publishTransaction(transactionID, rawTx);
132
+ nonceLock.releaseLock();
133
+ } catch (err) {
134
+ try {
135
+ this.setTxStatusFailed(transactionID, err);
136
+ } catch (err2) {
137
+ log.error(err2);
138
+ }
139
+ // must set transaction to submitted/failed before releasing lock
140
+ if (nonceLock) {
141
+ nonceLock.releaseLock();
142
+ }
143
+ // continue with error chain
144
+ throw err;
145
+ } finally {
146
+ this.inProcessOfSigning.delete(transactionID);
147
+ }
148
+ }
149
+ async signTransaction(txId) {
150
+ const txMeta = this.getTransaction(txId);
151
+ const chainId = this.getCurrentChainId();
152
+ const type = isEIP1559Transaction(txMeta) ? TRANSACTION_ENVELOPE_TYPES.FEE_MARKET : TRANSACTION_ENVELOPE_TYPES.LEGACY;
153
+ const txParams = _objectSpread(_objectSpread({}, txMeta.transaction), {}, {
154
+ type,
155
+ chainId,
156
+ gasLimit: txMeta.transaction.gas
157
+ });
158
+ const fromAddress = txParams.from;
159
+ const common = await this.getCommonConfiguration(fromAddress);
160
+ const {
161
+ TransactionFactory
162
+ } = await import('@ethereumjs/tx');
163
+ // TODO: fix this when @ethereumjs/tx is updated.
164
+ const unsignedEthTx = TransactionFactory.fromTxData(txParams, {
165
+ common
166
+ });
167
+ const signedEthTx = await this.signEthTx(unsignedEthTx, fromAddress);
168
+ txMeta.r = addHexPrefix(signedEthTx.r.toString(16));
169
+ txMeta.s = addHexPrefix(signedEthTx.s.toString(16));
170
+ txMeta.v = addHexPrefix(signedEthTx.v.toString(16));
171
+ this.updateTransactionInState(txMeta, "transactions#signTransaction: add r, s, v values");
172
+ this.setTxStatusSigned(txId);
173
+ const rawTx = addHexPrefix(Buffer.from(signedEthTx.serialize()).toString("hex"));
174
+ return rawTx;
175
+ }
176
+ async publishTransaction(txId, rawTx) {
177
+ const txMeta = this.getTransaction(txId);
178
+ txMeta.rawTransaction = rawTx;
179
+ this.updateTransactionInState(txMeta, "transactions#publishTransaction");
180
+ let txHash;
181
+ try {
182
+ txHash = await this.provider.request({
183
+ method: METHOD_TYPES.ETH_SEND_RAW_TRANSACTION,
184
+ params: [rawTx]
185
+ });
186
+ } catch (error) {
187
+ if (error.message.toLowerCase().includes("known transaction")) {
188
+ txHash = keccak256(addHexPrefix(rawTx));
189
+ txHash = addHexPrefix(txHash);
190
+ } else {
191
+ throw error;
192
+ }
193
+ }
194
+ this.setTxHash(txId, txHash);
195
+ this.setTxStatusSubmitted(txId);
196
+ }
197
+ async confirmTransaction(params) {
198
+ const {
199
+ txId,
200
+ txReceipt
201
+ } = params;
202
+ log.info(params, "confirm params");
203
+ const txMeta = this.getTransaction(txId);
204
+ if (!txMeta) return;
205
+ try {
206
+ txMeta.txReceipt = _objectSpread({}, txReceipt);
207
+ this.setTxStatusConfirmed(txId);
208
+ this.markNonceDuplicatesDropped(txId);
209
+ this.updateTransactionInState(txMeta, "transactions#confirmTransaction - add txReceipt");
210
+ } catch (error) {
211
+ log.error(error);
212
+ }
213
+ }
214
+ cancelTransaction(transactionID) {
215
+ throw new Error(`Method not implemented. ${transactionID}`);
216
+ }
217
+ async getEIP1559Compatibility(fromAddress) {
218
+ const currentNetworkIsCompatible = await this._getCurrentNetworkEIP1559Compatibility();
219
+ const fromAccountIsCompatible = await this._getCurrentAccountEIP1559Compatibility(fromAddress);
220
+ return currentNetworkIsCompatible && fromAccountIsCompatible;
221
+ }
222
+ async addTransactionGasDefaults(txMeta) {
223
+ let updateTxMeta = txMeta;
224
+ try {
225
+ updateTxMeta = await this.addTxGasDefaults(txMeta);
226
+ } catch (error) {
227
+ log.warn(error);
228
+ updateTxMeta = this.getTransaction(txMeta.id);
229
+ updateTxMeta.loadingDefaults = false;
230
+ this.updateTransactionInState(txMeta, "Failed to calculate gas defaults.");
231
+ throw error;
232
+ }
233
+ updateTxMeta.loadingDefaults = false;
234
+ this.updateTransactionInState(updateTxMeta, "Added new unapproved transaction.");
235
+ return updateTxMeta;
236
+ }
237
+ async addTxGasDefaults(txMeta) {
238
+ const eip1559Compatibility = txMeta.transaction.type !== TRANSACTION_ENVELOPE_TYPES.LEGACY && (await this.getEIP1559Compatibility());
239
+ const {
240
+ gasPrice: defaultGasPrice,
241
+ maxFeePerGas: defaultMaxFeePerGas,
242
+ maxPriorityFeePerGas: defaultMaxPriorityFeePerGas
243
+ } = await this.getDefaultGasFees(txMeta, eip1559Compatibility);
244
+ const {
245
+ gasLimit: defaultGasLimit,
246
+ simulationFails
247
+ } = await this.getDefaultGasLimit(txMeta);
248
+ txMeta = this.getTransaction(txMeta.id);
249
+ if (simulationFails) {
250
+ txMeta.simulationFails = simulationFails;
251
+ }
252
+ if (eip1559Compatibility) {
253
+ // If the dapp has suggested a gas price, but no maxFeePerGas or maxPriorityFeePerGas
254
+ // then we set maxFeePerGas and maxPriorityFeePerGas to the suggested gasPrice.
255
+ if (txMeta.transaction.gasPrice && !txMeta.transaction.maxFeePerGas && !txMeta.transaction.maxPriorityFeePerGas) {
256
+ txMeta.transaction.maxFeePerGas = txMeta.transaction.gasPrice;
257
+ // If the dapp has suggested a gas price, but no maxFeePerGas or maxPriorityFeePerGas
258
+ // then we set maxFeePerGas to the suggested gasPrice.
259
+
260
+ txMeta.transaction.maxPriorityFeePerGas = bnLessThan(typeof defaultMaxPriorityFeePerGas === "string" ? stripHexPrefix(defaultMaxPriorityFeePerGas) : defaultMaxPriorityFeePerGas, typeof txMeta.transaction.gasPrice === "string" ? stripHexPrefix(txMeta.transaction.gasPrice) : txMeta.transaction.gasPrice) ? defaultMaxPriorityFeePerGas : txMeta.transaction.gasPrice;
261
+ } else {
262
+ if (defaultMaxFeePerGas && !txMeta.transaction.maxFeePerGas) {
263
+ // If the dapp has not set the gasPrice or the maxFeePerGas, then we set maxFeePerGas
264
+ // with the one returned by the gasFeeController, if that is available.
265
+ txMeta.transaction.maxFeePerGas = defaultMaxFeePerGas;
266
+ }
267
+ if (defaultMaxPriorityFeePerGas && !txMeta.transaction.maxPriorityFeePerGas) {
268
+ // If the dapp has not set the gasPrice or the maxPriorityFeePerGas, then we set maxPriorityFeePerGas
269
+ // with the one returned by the gasFeeController, if that is available.
270
+ txMeta.transaction.maxPriorityFeePerGas = defaultMaxPriorityFeePerGas;
271
+ }
272
+ if (defaultGasPrice && !txMeta.transaction.maxFeePerGas) {
273
+ // If the dapp has not set the gasPrice or the maxFeePerGas, and no maxFeePerGas is available
274
+ // from the gasFeeController, then we set maxFeePerGas to the defaultGasPrice, assuming it is
275
+ // available.
276
+ txMeta.transaction.maxFeePerGas = defaultGasPrice;
277
+ }
278
+ if (txMeta.transaction.maxFeePerGas && !txMeta.transaction.maxPriorityFeePerGas) {
279
+ // If the dapp has not set the gasPrice or the maxPriorityFeePerGas, and no maxPriorityFeePerGas is
280
+ // available from the gasFeeController, then we set maxPriorityFeePerGas to
281
+ // txMeta.transaction.maxFeePerGas, which will either be the gasPrice from the controller, the maxFeePerGas
282
+ // set by the dapp, or the maxFeePerGas from the controller.
283
+ txMeta.transaction.maxPriorityFeePerGas = txMeta.transaction.maxFeePerGas;
284
+ }
285
+ }
286
+
287
+ // We remove the gasPrice param entirely when on an eip1559 compatible network
288
+
289
+ delete txMeta.transaction.gasPrice;
290
+ } else {
291
+ // We ensure that maxFeePerGas and maxPriorityFeePerGas are not in the transaction params
292
+ // when not on a EIP1559 compatible network
293
+
294
+ delete txMeta.transaction.maxPriorityFeePerGas;
295
+ delete txMeta.transaction.maxFeePerGas;
296
+ }
297
+
298
+ // If we have gotten to this point, and none of gasPrice, maxPriorityFeePerGas or maxFeePerGas are
299
+ // set on transaction, it means that either we are on a non-EIP1559 network and the dapp didn't suggest
300
+ // a gas price, or we are on an EIP1559 network, and none of gasPrice, maxPriorityFeePerGas or maxFeePerGas
301
+ // were available from either the dapp or the network.
302
+ if (defaultGasPrice && !txMeta.transaction.gasPrice && !txMeta.transaction.maxPriorityFeePerGas && !txMeta.transaction.maxFeePerGas) {
303
+ txMeta.transaction.gasPrice = defaultGasPrice;
304
+ }
305
+ if (defaultGasLimit && !txMeta.transaction.gas) {
306
+ txMeta.transaction.gas = defaultGasLimit;
307
+ }
308
+ return txMeta;
309
+ }
310
+ setTxHash(txId, txHash) {
311
+ // Add the tx hash to the persisted meta-tx object
312
+ const txMeta = this.getTransaction(txId);
313
+ txMeta.transactionHash = txHash;
314
+ this.updateTransactionInState(txMeta, "transactions#setTxHash");
315
+ }
316
+ async getDefaultGasFees(txMeta, eip1559Compatibility) {
317
+ if (!eip1559Compatibility && txMeta.transaction.gasPrice || eip1559Compatibility && txMeta.transaction.maxFeePerGas && txMeta.transaction.maxPriorityFeePerGas) {
318
+ return {};
319
+ }
320
+ try {
321
+ const {
322
+ gasFeeEstimates,
323
+ gasEstimateType
324
+ } = await this.getEIP1559GasFeeEstimates();
325
+ if (eip1559Compatibility && gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {
326
+ // this is in dec gwei
327
+ const {
328
+ medium: {
329
+ suggestedMaxPriorityFeePerGas,
330
+ suggestedMaxFeePerGas
331
+ } = {}
332
+ } = gasFeeEstimates;
333
+ if (suggestedMaxPriorityFeePerGas && suggestedMaxFeePerGas) {
334
+ return {
335
+ // send to controller in hex wei
336
+ maxFeePerGas: addHexPrefix(decGWEIToHexWEI(new BigNumber(suggestedMaxFeePerGas)).toString(16)),
337
+ maxPriorityFeePerGas: addHexPrefix(decGWEIToHexWEI(new BigNumber(suggestedMaxPriorityFeePerGas)).toString(16))
338
+ };
339
+ }
340
+ } else if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) {
341
+ const {
342
+ medium
343
+ } = gasFeeEstimates;
344
+ // The LEGACY type includes low, medium and high estimates of
345
+ // gas price values.
346
+ return {
347
+ gasPrice: addHexPrefix(decGWEIToHexWEI(new BigNumber(medium)).toString(16))
348
+ };
349
+ } else if (gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE) {
350
+ const {
351
+ gasPrice
352
+ } = gasFeeEstimates;
353
+ // The ETH_GASPRICE type just includes a single gas price property,
354
+ // which we can assume was retrieved from eth_gasPrice
355
+ return {
356
+ gasPrice: addHexPrefix(decGWEIToHexWEI(new BigNumber(gasPrice)).toString(16))
357
+ };
358
+ }
359
+ } catch (error) {
360
+ log.error(error);
361
+ }
362
+ const gasPrice = await this.provider.request({
363
+ method: METHOD_TYPES.ETH_GET_GAS_PRICE
364
+ });
365
+ return {
366
+ gasPrice: gasPrice && addHexPrefix(gasPrice)
367
+ };
368
+ }
369
+ async getDefaultGasLimit(txMeta) {
370
+ const chainId = this.getCurrentChainId();
371
+ const customNetworkGasBuffer = CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP[chainId];
372
+ const chainType = getChainType(chainId);
373
+ if (txMeta.transaction.gas) {
374
+ return {};
375
+ }
376
+ if (txMeta.transaction.to && txMeta.transactionCategory === TRANSACTION_TYPES.SENT_ETHER && chainType !== "custom" && !txMeta.transaction.data) {
377
+ // This is a standard ether simple send, gas requirement is exactly 21k
378
+ return {
379
+ gasLimit: GAS_LIMITS.SIMPLE
380
+ };
381
+ }
382
+ const {
383
+ blockGasLimit,
384
+ estimatedGasHex,
385
+ simulationFails
386
+ } = await this.txGasUtil.analyzeGasUsage(txMeta);
387
+
388
+ // add additional gas buffer to our estimation for safety
389
+ const gasLimit = this.txGasUtil.addGasBuffer(addHexPrefix(estimatedGasHex), blockGasLimit, customNetworkGasBuffer);
390
+ return {
391
+ gasLimit,
392
+ simulationFails
393
+ };
394
+ }
395
+ async createTransaction(txParameters, req) {
396
+ const normalizedTxParameters = normalizeTxParameters(txParameters);
397
+ const eip1559Compatibility = await this.getEIP1559Compatibility(txParameters.from);
398
+ validateTxParameters(normalizedTxParameters, eip1559Compatibility);
399
+ let txMeta = this.generateTxMeta({
400
+ transaction: normalizedTxParameters,
401
+ origin: req.origin
402
+ });
403
+ const {
404
+ type,
405
+ category,
406
+ methodParams
407
+ } = await determineTransactionType(txParameters, this.provider);
408
+ txMeta.contractType = type;
409
+ txMeta.transactionCategory = category;
410
+ txMeta.methodParams = methodParams;
411
+ txMeta.transaction.value = txMeta.transaction.value ? addHexPrefix(txMeta.transaction.value) : "0x0";
412
+ this.emit(`${txMeta.id}:unapproved`, txMeta);
413
+ txMeta = this.addTransactionToState(txMeta);
414
+ txMeta = await this.addTransactionGasDefaults(txMeta);
415
+ this.emit(TX_EVENTS.TX_UNAPPROVED, {
416
+ txMeta,
417
+ req
418
+ });
419
+ return txMeta;
420
+ }
421
+ _setupListeners() {
422
+ this.setupBlockTrackerListener();
423
+ this.pendingTxTracker.on(TX_EVENTS.TX_WARNING, data => {
424
+ this.updateTransactionInState(data.txMeta);
425
+ });
426
+ this.pendingTxTracker.on(TX_EVENTS.TX_DROPPED, data => this.setTxStatusDropped(data.txId));
427
+ this.pendingTxTracker.on(TX_EVENTS.TX_BLOCK_UPDATE, ({
428
+ txMeta,
429
+ latestBlockNumber
430
+ }) => {
431
+ if (!txMeta.firstRetryBlockNumber) {
432
+ txMeta.firstRetryBlockNumber = latestBlockNumber;
433
+ this.updateTransactionInState(txMeta);
434
+ }
435
+ });
436
+ this.pendingTxTracker.on(TX_EVENTS.TX_RETRY, data => {
437
+ if (!("retryCount" in data.txMeta)) {
438
+ data.txMeta.retryCount = 0;
439
+ }
440
+ data.txMeta.retryCount += 1;
441
+ this.updateTransactionInState(data.txMeta);
442
+ });
443
+ this.pendingTxTracker.on(TX_EVENTS.TX_FAILED, data => {
444
+ this.setTxStatusFailed(data.txId, data.error);
445
+ });
446
+ this.pendingTxTracker.on(TX_EVENTS.TX_CONFIRMED, data => this.confirmTransaction(data));
447
+ }
448
+ setupBlockTrackerListener() {
449
+ let listenersAreActive = false;
450
+ const latestBlockHandler = this.onLatestBlock.bind(this);
451
+ this.on(TX_EVENTS.TX_STATUS_UPDATE, () => {
452
+ const pendingTxs = this.getPendingTransactions();
453
+ if (!listenersAreActive && pendingTxs.length > 0) {
454
+ this.blockTracker.on("latest", latestBlockHandler);
455
+ listenersAreActive = true;
456
+ } else if (listenersAreActive && !pendingTxs.length) {
457
+ this.blockTracker.removeListener("latest", latestBlockHandler);
458
+ listenersAreActive = false;
459
+ }
460
+ });
461
+ }
462
+ async onLatestBlock(blockNumber) {
463
+ try {
464
+ await this.pendingTxTracker.updatePendingTxs();
465
+ } catch (error) {
466
+ log.error(error);
467
+ }
468
+ try {
469
+ await this.pendingTxTracker.resubmitPendingTxs(blockNumber);
470
+ } catch (error) {
471
+ log.error(error);
472
+ }
473
+ }
474
+ async getCommonConfiguration(fromAddress) {
475
+ const {
476
+ chainId,
477
+ displayName
478
+ } = this.getProviderConfig();
479
+ const supportsEIP1559 = await this.getEIP1559Compatibility(fromAddress);
480
+ const {
481
+ Common,
482
+ Hardfork
483
+ } = await import('@ethereumjs/common');
484
+ const hardfork = supportsEIP1559 ? Hardfork.Paris : Hardfork.Berlin;
485
+ return Common.custom({
486
+ chainId: chainId === "loading" ? 0 : Number.parseInt(chainId, 16),
487
+ defaultHardfork: hardfork,
488
+ name: displayName,
489
+ networkId: chainId === "loading" ? 0 : Number.parseInt(chainId, 16)
490
+ });
491
+ }
492
+ markNonceDuplicatesDropped(txId) {
493
+ const txMeta = this.getTransaction(txId);
494
+ const {
495
+ nonce,
496
+ from
497
+ } = txMeta.transaction;
498
+ const sameNonceTxs = this.getTransactions({
499
+ searchCriteria: {
500
+ from,
501
+ nonce
502
+ }
503
+ });
504
+ if (!sameNonceTxs.length) return;
505
+ sameNonceTxs.forEach(tx => {
506
+ if (tx.id === txId) return;
507
+ this.updateTransactionInState(txMeta, "transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce");
508
+ if (tx.status !== TransactionStatus.failed) this.setTxStatusDropped(tx.id);
509
+ });
510
+ }
511
+ }
512
+
513
+ export { TransactionController };
@@ -0,0 +1,79 @@
1
+ import _defineProperty from '@babel/runtime/helpers/defineProperty';
2
+ import { stripHexPrefix, addHexPrefix } from '@ethereumjs/util';
3
+ import { cloneDeep } from '@toruslabs/base-controllers';
4
+ import { BN } from 'bn.js';
5
+ import log from 'loglevel';
6
+
7
+ class TransactionGasUtil {
8
+ constructor(provider, blockTracker) {
9
+ _defineProperty(this, "provider", void 0);
10
+ _defineProperty(this, "blockTracker", void 0);
11
+ this.provider = provider;
12
+ this.blockTracker = blockTracker;
13
+ }
14
+ async analyzeGasUsage(txMeta) {
15
+ const block = await this.blockTracker.getLatestBlock();
16
+ // fallback to block gasLimit
17
+ const blockGasLimitBN = new BN(stripHexPrefix(block.gasLimit), 16);
18
+ const saferGasLimitBN = blockGasLimitBN.mul(new BN(19)).div(new BN(20));
19
+ let estimatedGasHex = addHexPrefix(saferGasLimitBN.toString("hex"));
20
+ let simulationFails;
21
+ try {
22
+ estimatedGasHex = await this.estimateTxGas(txMeta);
23
+ } catch (error) {
24
+ log.warn(error);
25
+ simulationFails = {
26
+ reason: error.message,
27
+ errorKey: error.errorKey,
28
+ debug: {
29
+ blockNumber: block.idempotencyKey,
30
+ blockGasLimit: block.gasLimit
31
+ }
32
+ };
33
+ }
34
+ return {
35
+ blockGasLimit: block.gasLimit,
36
+ estimatedGasHex,
37
+ simulationFails
38
+ };
39
+ }
40
+
41
+ /**
42
+ Adds a gas buffer with out exceeding the block gas limit
43
+ */
44
+ addGasBuffer(initialGasLimitHex, blockGasLimitHex, multiplier = 1.5) {
45
+ const initialGasLimitBn = new BN(stripHexPrefix(initialGasLimitHex), 16);
46
+ const blockGasLimitBn = new BN(stripHexPrefix(blockGasLimitHex), 16);
47
+ const upperGasLimitBn = blockGasLimitBn.muln(0.9);
48
+ const bufferedGasLimitBn = initialGasLimitBn.muln(multiplier);
49
+
50
+ // if initialGasLimit is above blockGasLimit, dont modify it
51
+ if (initialGasLimitBn.gt(upperGasLimitBn)) return addHexPrefix(initialGasLimitBn.toString("hex"));
52
+ // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit
53
+ if (bufferedGasLimitBn.lt(upperGasLimitBn)) return addHexPrefix(bufferedGasLimitBn.toString("hex"));
54
+ // otherwise use blockGasLimit
55
+ return addHexPrefix(upperGasLimitBn.toString("hex"));
56
+ }
57
+
58
+ /**
59
+ Estimates the tx's gas usage
60
+ */
61
+ async estimateTxGas(txMeta) {
62
+ const txParams = cloneDeep(txMeta.transaction);
63
+
64
+ // `eth_estimateGas` can fail if the user has insufficient balance for the
65
+ // value being sent, or for the gas cost. We don't want to check their
66
+ // balance here, we just want the gas estimate. The gas price is removed
67
+ // to skip those balance checks. We check balance elsewhere. We also delete
68
+ // maxFeePerGas and maxPriorityFeePerGas to support EIP-1559 txs.
69
+ delete txParams.gasPrice;
70
+ delete txParams.maxFeePerGas;
71
+ delete txParams.maxPriorityFeePerGas;
72
+ return this.provider.request({
73
+ method: "eth_estimateGas",
74
+ params: [txParams]
75
+ });
76
+ }
77
+ }
78
+
79
+ export { TransactionGasUtil };
@@ -0,0 +1,38 @@
1
+ import _objectSpread from '@babel/runtime/helpers/objectSpread2';
2
+ import { cloneDeep } from '@toruslabs/base-controllers';
3
+ import jsonDiffer from 'fast-json-patch';
4
+
5
+ /**
6
+ Generates an array of history objects sense the previous state.
7
+ The object has the keys
8
+ op (the operation performed),
9
+ path (the key and if a nested object then each key will be seperated with a `/`)
10
+ value
11
+ with the first entry having the note and a timestamp when the change took place
12
+ */
13
+ function generateHistoryEntry(previousState, newState, note) {
14
+ const entry = jsonDiffer.compare(previousState, newState);
15
+ // Add a note to the first op, since it breaks if we append it to the entry
16
+ if (entry[0]) {
17
+ if (note) {
18
+ entry[0].note = note;
19
+ }
20
+ entry[0].timestamp = Date.now();
21
+ }
22
+ return entry;
23
+ }
24
+
25
+ /**
26
+ Recovers previous txMeta state obj
27
+ */
28
+ function replayHistory(_shortHistory) {
29
+ const shortHistory = cloneDeep(_shortHistory);
30
+ return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument);
31
+ }
32
+ function snapshotFromTxMeta(txMeta) {
33
+ const shallow = _objectSpread({}, txMeta);
34
+ delete shallow.history;
35
+ return cloneDeep(shallow);
36
+ }
37
+
38
+ export { generateHistoryEntry, replayHistory, snapshotFromTxMeta };