@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.
- package/dist/ethereumControllers.cjs.js +114 -432
- package/dist/ethereumControllers.esm.js +63 -351
- package/dist/ethereumControllers.umd.min.js +1 -1
- package/dist/ethereumControllers.umd.min.js.LICENSE.txt +7 -2
- package/dist/lib.cjs/Account/AccountTrackerController.js +160 -0
- package/dist/lib.cjs/Block/PollingBlockTracker.js +85 -0
- package/dist/lib.cjs/Currency/CurrencyController.js +111 -0
- package/dist/lib.cjs/Gas/GasFeeController.js +214 -0
- package/dist/lib.cjs/Gas/gasUtil.js +148 -0
- package/dist/lib.cjs/Keyring/KeyringController.js +93 -0
- package/dist/lib.cjs/Message/AbstractMessageController.js +107 -0
- package/dist/lib.cjs/Message/AddChainController.js +78 -0
- package/dist/lib.cjs/Message/MessageController.js +77 -0
- package/dist/lib.cjs/Message/PersonalMessageController.js +77 -0
- package/dist/lib.cjs/Message/SwitchChainController.js +78 -0
- package/dist/lib.cjs/Message/TypedMessageController.js +81 -0
- package/dist/lib.cjs/Message/utils.js +112 -0
- package/dist/lib.cjs/Network/NetworkController.js +201 -0
- package/dist/lib.cjs/Network/cacheIdentifier.js +112 -0
- package/dist/lib.cjs/Network/createEthereumMiddleware.js +302 -0
- package/dist/lib.cjs/Network/createJsonRpcClient.js +64 -0
- package/dist/lib.cjs/Nfts/NftHandler.js +180 -0
- package/dist/lib.cjs/Nfts/NftsController.js +213 -0
- package/dist/lib.cjs/Preferences/PreferencesController.js +476 -0
- package/dist/lib.cjs/Tokens/TokenHandler.js +51 -0
- package/dist/lib.cjs/Tokens/TokenRatesController.js +112 -0
- package/dist/lib.cjs/Tokens/TokensController.js +259 -0
- package/dist/lib.cjs/Transaction/NonceTracker.js +150 -0
- package/dist/lib.cjs/Transaction/PendingTransactionTracker.js +222 -0
- package/dist/lib.cjs/Transaction/TransactionController.js +515 -0
- package/dist/lib.cjs/Transaction/TransactionGasUtil.js +81 -0
- package/dist/lib.cjs/Transaction/TransactionStateHistoryHelper.js +42 -0
- package/dist/lib.cjs/Transaction/TransactionStateManager.js +296 -0
- package/dist/lib.cjs/Transaction/TransactionUtils.js +341 -0
- package/dist/lib.cjs/index.js +171 -0
- package/dist/lib.cjs/utils/abis.js +510 -0
- package/dist/lib.cjs/utils/constants.js +362 -0
- package/dist/lib.cjs/utils/contractAddresses.js +16 -0
- package/dist/lib.cjs/utils/conversionUtils.js +232 -0
- package/dist/lib.cjs/utils/helpers.js +244 -0
- package/dist/lib.cjs/utils/lodashUtils.js +25 -0
- package/dist/lib.esm/Account/AccountTrackerController.js +158 -0
- package/dist/lib.esm/Block/PollingBlockTracker.js +83 -0
- package/dist/lib.esm/Currency/CurrencyController.js +109 -0
- package/dist/lib.esm/Gas/GasFeeController.js +212 -0
- package/dist/lib.esm/Gas/gasUtil.js +141 -0
- package/dist/lib.esm/Keyring/KeyringController.js +91 -0
- package/dist/lib.esm/Message/AbstractMessageController.js +105 -0
- package/dist/lib.esm/Message/AddChainController.js +76 -0
- package/dist/lib.esm/Message/MessageController.js +75 -0
- package/dist/lib.esm/Message/PersonalMessageController.js +75 -0
- package/dist/lib.esm/Message/SwitchChainController.js +76 -0
- package/dist/lib.esm/Message/TypedMessageController.js +79 -0
- package/dist/lib.esm/Message/utils.js +105 -0
- package/dist/lib.esm/Network/NetworkController.js +199 -0
- package/dist/lib.esm/Network/cacheIdentifier.js +107 -0
- package/dist/lib.esm/Network/createEthereumMiddleware.js +289 -0
- package/dist/lib.esm/Network/createJsonRpcClient.js +60 -0
- package/dist/lib.esm/Nfts/NftHandler.js +178 -0
- package/dist/lib.esm/Nfts/NftsController.js +211 -0
- package/dist/lib.esm/Preferences/PreferencesController.js +474 -0
- package/dist/lib.esm/Tokens/TokenHandler.js +49 -0
- package/dist/lib.esm/Tokens/TokenRatesController.js +109 -0
- package/dist/lib.esm/Tokens/TokensController.js +257 -0
- package/dist/lib.esm/Transaction/NonceTracker.js +148 -0
- package/dist/lib.esm/Transaction/PendingTransactionTracker.js +220 -0
- package/dist/lib.esm/Transaction/TransactionController.js +513 -0
- package/dist/lib.esm/Transaction/TransactionGasUtil.js +79 -0
- package/dist/lib.esm/Transaction/TransactionStateHistoryHelper.js +38 -0
- package/dist/lib.esm/Transaction/TransactionStateManager.js +294 -0
- package/dist/lib.esm/Transaction/TransactionUtils.js +326 -0
- package/dist/lib.esm/index.js +33 -0
- package/dist/lib.esm/utils/abis.js +505 -0
- package/dist/lib.esm/utils/constants.js +323 -0
- package/dist/lib.esm/utils/contractAddresses.js +14 -0
- package/dist/lib.esm/utils/conversionUtils.js +218 -0
- package/dist/lib.esm/utils/helpers.js +227 -0
- package/dist/lib.esm/utils/lodashUtils.js +21 -0
- package/dist/types/Account/AccountTrackerController.d.ts +5 -5
- package/dist/types/Block/PollingBlockTracker.d.ts +1 -2
- package/dist/types/Currency/CurrencyController.d.ts +1 -1
- package/dist/types/Gas/GasFeeController.d.ts +3 -3
- package/dist/types/Gas/gasUtil.d.ts +1 -1
- package/dist/types/Keyring/KeyringController.d.ts +3 -5
- package/dist/types/Message/AbstractMessageController.d.ts +5 -6
- package/dist/types/Message/AddChainController.d.ts +4 -4
- package/dist/types/Message/MessageController.d.ts +4 -4
- package/dist/types/Message/PersonalMessageController.d.ts +4 -4
- package/dist/types/Message/SwitchChainController.d.ts +4 -4
- package/dist/types/Message/TypedMessageController.d.ts +6 -7
- package/dist/types/Message/utils.d.ts +2 -7
- package/dist/types/Network/NetworkController.d.ts +4 -4
- package/dist/types/Network/cacheIdentifier.d.ts +1 -1
- package/dist/types/Network/createEthereumMiddleware.d.ts +2 -18
- package/dist/types/Network/createJsonRpcClient.d.ts +2 -2
- package/dist/types/Nfts/NftsController.d.ts +2 -2
- package/dist/types/Preferences/PreferencesController.d.ts +4 -4
- package/dist/types/Tokens/TokensController.d.ts +3 -3
- package/dist/types/Transaction/NonceTracker.d.ts +5 -5
- package/dist/types/Transaction/PendingTransactionTracker.d.ts +5 -5
- package/dist/types/Transaction/TransactionController.d.ts +12 -12
- package/dist/types/Transaction/TransactionGasUtil.d.ts +4 -4
- package/dist/types/Transaction/TransactionStateManager.d.ts +3 -3
- package/dist/types/Transaction/TransactionUtils.d.ts +1 -1
- package/dist/types/index.d.ts +12 -14
- package/dist/types/utils/constants.d.ts +1 -5
- package/dist/types/utils/helpers.d.ts +7 -4
- package/dist/types/utils/interfaces.d.ts +43 -23
- package/package.json +22 -10
- package/dist/types/Message/DecryptMessageController.d.ts +0 -20
- 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 };
|