@toruslabs/ethereum-controllers 5.3.4 → 5.3.6

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.
@@ -1,18 +1,18 @@
1
1
  import _objectSpread from '@babel/runtime/helpers/objectSpread2';
2
2
  import _defineProperty from '@babel/runtime/helpers/defineProperty';
3
- import { CHAIN_NAMESPACES, BaseController, formatSmallNumbers, addressSlicer, ACTIVITY_ACTION_RECEIVE, ACTIVITY_ACTION_SEND, significantDigits, TransactionStatus, BaseBlockTracker, timeout, BaseCurrencyController, BaseKeyringController, randomId, PROVIDER_JRPC_METHODS, createFetchMiddleware, createSwappableProxy, createEventEmitterProxy, BasePreferencesController, TX_EVENTS, TRANSACTION_TYPES, BaseTransactionStateManager, transactionMatchesNetwork as transactionMatchesNetwork$1 } from '@toruslabs/base-controllers';
3
+ import { CHAIN_NAMESPACES, BaseController, randomId, TransactionStatus, TRANSACTION_TYPES, formatSmallNumbers, addressSlicer, ACTIVITY_ACTION_RECEIVE, ACTIVITY_ACTION_SEND, significantDigits, BaseBlockTracker, timeout, BaseCurrencyController, BaseKeyringController, PROVIDER_JRPC_METHODS, createFetchMiddleware, createSwappableProxy, createEventEmitterProxy, BasePreferencesController, TX_EVENTS, BaseTransactionStateManager, transactionMatchesNetwork as transactionMatchesNetwork$1 } from '@toruslabs/base-controllers';
4
4
  import { Mutex } from 'async-mutex';
5
- import { BrowserProvider, toQuantity, Contract, SigningKey, Wallet, isHexString as isHexString$1, JsonRpcProvider, Interface, keccak256 } from 'ethers';
5
+ import { BrowserProvider, toQuantity, Contract, Interface, formatEther, SigningKey, Wallet, isHexString as isHexString$1, JsonRpcProvider, keccak256 } from 'ethers';
6
6
  import log from 'loglevel';
7
- import { addHexPrefix, isValidAddress, toChecksumAddress, stripHexPrefix, isHexString, ecsign, bigIntToBytes, bytesToHex } from '@ethereumjs/util';
7
+ import { isHexString, addHexPrefix, isValidAddress, toChecksumAddress, stripHexPrefix, ecsign, bigIntToBytes, bytesToHex } from '@ethereumjs/util';
8
8
  import BigNumber from 'bignumber.js';
9
+ import { rpcErrors, providerErrors } from '@metamask/rpc-errors';
9
10
  import { get, post, remove, patch } from '@toruslabs/http-helpers';
10
11
  import { cloneDeep, merge, omitBy, mapValues, keyBy, sortBy, pickBy } from 'lodash';
11
12
  import _objectDestructuringEmpty from '@babel/runtime/helpers/objectDestructuringEmpty';
12
13
  import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties';
13
14
  import BN, { BN as BN$1 } from 'bn.js';
14
15
  import { concatSig, personalSign, signTypedData, getEncryptionPublicKey, decrypt, typedSignatureHash, TYPED_MESSAGE_SCHEMA, SignTypedDataVersion } from '@metamask/eth-sig-util';
15
- import { providerErrors, rpcErrors } from '@metamask/rpc-errors';
16
16
  import { validate } from 'jsonschema';
17
17
  import { createAsyncMiddleware, mergeMiddleware, createScaffoldMiddleware, providerFromMiddleware, JRPCEngine, providerFromEngine, SafeEventEmitter } from '@toruslabs/openlogin-jrpc';
18
18
  import { Hardfork, Common } from '@ethereumjs/common';
@@ -578,7 +578,7 @@ const SUPPORTED_NETWORKS = {
578
578
  blockExplorerUrl: "https://bscscan.com",
579
579
  chainId: BSC_MAINNET_CHAIN_ID,
580
580
  displayName: "Binance Smart Chain (BSC)",
581
- logo: "bnb_logo.png",
581
+ logo: "bnb_logo.svg",
582
582
  rpcTarget: `https://bsc-dataseed.binance.org`,
583
583
  ticker: "BNB",
584
584
  tickerName: "Binance Coin"
@@ -633,7 +633,7 @@ const SUPPORTED_NETWORKS = {
633
633
  blockExplorerUrl: "https://gnosis.blockscout.com",
634
634
  chainId: XDAI_CHAIN_ID,
635
635
  displayName: "xDai",
636
- logo: "xdai.svg",
636
+ logo: "xDai.svg",
637
637
  rpcTarget: `https://rpc.gnosischain.com`,
638
638
  ticker: "DAI",
639
639
  tickerName: "xDai Token"
@@ -679,7 +679,7 @@ const SUPPORTED_NETWORKS = {
679
679
  blockExplorerUrl: "https://testnet.bscscan.com",
680
680
  chainId: BSC_TESTNET_CHAIN_ID,
681
681
  displayName: "Binance Smart Chain Testnet",
682
- logo: "bnb_logo.png",
682
+ logo: "bnb_logo.svg",
683
683
  rpcTarget: `https://data-seed-prebsc-1-s1.binance.org:8545`,
684
684
  ticker: "BNB",
685
685
  tickerName: "Binance Coin",
@@ -1015,422 +1015,798 @@ class AccountTrackerController extends BaseController {
1015
1015
  }
1016
1016
  }
1017
1017
 
1018
- function getEtherScanHashLink(txHash, chainId) {
1019
- if (!SUPPORTED_NETWORKS[chainId]) return "";
1020
- return `${SUPPORTED_NETWORKS[chainId].blockExplorerUrl}/tx/${txHash}`;
1021
- }
1022
- const formatPastTx = (x, lowerCaseSelectedAddress) => {
1023
- var _x$to;
1024
- let totalAmountString = "";
1025
- if (x.type === CONTRACT_TYPE_ERC721 || x.type === CONTRACT_TYPE_ERC1155) totalAmountString = x.symbol;else if (x.type === CONTRACT_TYPE_ERC20) totalAmountString = formatSmallNumbers(Number.parseFloat(x.total_amount), x.symbol, true);else totalAmountString = formatSmallNumbers(Number.parseFloat(x.total_amount), x.type_name, true);
1026
- const currencyAmountString = x.type === CONTRACT_TYPE_ERC721 || x.type === CONTRACT_TYPE_ERC1155 ? "" : formatSmallNumbers(Number.parseFloat(x.currency_amount), x.selected_currency, true);
1027
- const finalObject = {
1028
- id: x.created_at.toString(),
1029
- date: new Date(x.created_at).toString(),
1030
- from: x.from,
1031
- from_aa_address: x.from_aa_address,
1032
- slicedFrom: typeof x.from === "string" ? addressSlicer(x.from) : "",
1033
- to: x.to,
1034
- slicedTo: typeof x.to === "string" ? addressSlicer(x.to) : "",
1035
- action: lowerCaseSelectedAddress === ((_x$to = x.to) === null || _x$to === void 0 ? void 0 : _x$to.toLowerCase()) || "" ? ACTIVITY_ACTION_RECEIVE : ACTIVITY_ACTION_SEND,
1036
- totalAmount: x.total_amount,
1037
- totalAmountString,
1038
- currencyAmount: x.currency_amount,
1039
- currencyAmountString,
1040
- amount: `${totalAmountString} / ${currencyAmountString}`,
1041
- status: x.status,
1042
- etherscanLink: getEtherScanHashLink(x.transaction_hash, x.chain_id || MAINNET_CHAIN_ID),
1043
- chainId: x.chain_id,
1044
- ethRate: Number.parseFloat(x === null || x === void 0 ? void 0 : x.total_amount) && Number.parseFloat(x === null || x === void 0 ? void 0 : x.currency_amount) ? `1 ${x.symbol} = ${significantDigits(Number.parseFloat(x.currency_amount) / Number.parseFloat(x.total_amount))}` : "",
1045
- currencyUsed: x.selected_currency,
1046
- type: x.type,
1047
- type_name: x.type_name,
1048
- type_image_link: x.type_image_link,
1049
- transaction_hash: x.transaction_hash,
1050
- transaction_category: x.transaction_category,
1051
- // TODO: // figure out how to handle these values.
1052
- // isEtherscan: x.isEtherscan,
1053
- // input: x.input || "",
1054
- // token_id: x.token_id || "",
1055
- contract_address: x.contract_address || "",
1056
- nonce: x.nonce || "",
1057
- is_cancel: !!x.is_cancel || false,
1058
- gas: x.gas || "",
1059
- gasPrice: x.gasPrice || ""
1060
- };
1061
- return finalObject;
1018
+ const erc20Interface = new Interface(erc20Abi);
1019
+ const erc721Interface = new Interface(erc721Abi);
1020
+ const erc1155Interface = new Interface(erc1155Abi);
1021
+
1022
+ // functions that handle normalizing of that key in txParams
1023
+
1024
+ const normalizers = {
1025
+ from: function (from) {
1026
+ let LowerCase = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
1027
+ return LowerCase ? addHexPrefix(from).toLowerCase() : addHexPrefix(from);
1028
+ },
1029
+ to: function (to) {
1030
+ let LowerCase = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
1031
+ return LowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to);
1032
+ },
1033
+ nonce: nonce => addHexPrefix(nonce),
1034
+ customNonceValue: nonce => addHexPrefix(nonce),
1035
+ value: value => addHexPrefix(value),
1036
+ data: data => addHexPrefix(data),
1037
+ gas: gas => addHexPrefix(gas),
1038
+ gasPrice: gasPrice => addHexPrefix(gasPrice),
1039
+ type: addHexPrefix,
1040
+ maxFeePerGas: addHexPrefix,
1041
+ maxPriorityFeePerGas: addHexPrefix
1062
1042
  };
1063
1043
 
1064
1044
  /**
1065
- * Ref - https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionreceipt
1045
+ * normalizes txParams
1066
1046
  */
1067
- const getEthTxStatus = async (hash, provider) => {
1068
- try {
1069
- const result = await provider.request({
1070
- method: METHOD_TYPES.ETH_GET_TRANSACTION_RECEIPT,
1071
- params: [hash]
1072
- });
1073
- if (result === null) return TransactionStatus.submitted;
1074
- if (result && result.status === "0x1") return TransactionStatus.confirmed;
1075
- if (result && result.status === "0x0") return TransactionStatus.rejected;
1076
- return undefined;
1077
- } catch (err) {
1078
- log.warn("unable to fetch transaction status", err);
1079
- return undefined;
1080
- }
1081
- };
1082
- function formatDate(inputDate) {
1083
- const monthList = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1084
- const date = new Date(inputDate);
1085
- const day = date.getDate();
1086
- const month = monthList[date.getMonth()];
1087
- const year = date.getFullYear();
1088
- return `${day} ${month} ${year}`;
1089
- }
1090
- function formatTime(time) {
1091
- return new Date(time).toTimeString().slice(0, 8);
1092
- }
1093
- const idleTimeTracker = (activityThresholdTime => {
1094
- let isIdle = false;
1095
- let idleTimeout = null;
1096
- const resetTimer = () => {
1097
- if (idleTimeout) {
1098
- window.clearTimeout(idleTimeout);
1099
- }
1100
- isIdle = false;
1101
- idleTimeout = window.setTimeout(() => {
1102
- isIdle = true;
1103
- }, activityThresholdTime * 1000);
1047
+ function normalizeTxParameters(txParameters) {
1048
+ let lowerCase = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
1049
+ // apply only keys in the normalizers
1050
+ const normalizedTxParameters = {
1051
+ id: txParameters.id || randomId(),
1052
+ from: txParameters.from
1104
1053
  };
1105
- if (typeof window !== "undefined" && typeof document !== "undefined") {
1106
- window.addEventListener("load", resetTimer);
1107
- document.addEventListener("mousemove", resetTimer);
1108
- document.addEventListener("keydown", resetTimer);
1054
+ for (const key in normalizers) {
1055
+ const currentKey = key;
1056
+ if (txParameters[currentKey])
1057
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1058
+ normalizedTxParameters[currentKey] = normalizers[currentKey](txParameters[currentKey], lowerCase);
1109
1059
  }
1110
- function checkIfIdle() {
1111
- return isIdle;
1060
+ return normalizedTxParameters;
1061
+ }
1062
+ function transactionMatchesNetwork(transaction, chainId) {
1063
+ if (typeof transaction.chainId !== "undefined") {
1064
+ return transaction.chainId === chainId;
1112
1065
  }
1113
- return {
1114
- checkIfIdle
1115
- };
1116
- })(60 * 3);
1117
- function isAddressByChainId(address, _chainId) {
1118
- // TOOD: add rsk network checks.
1119
- return isValidAddress(address);
1066
+ return false;
1120
1067
  }
1121
- function toChecksumAddressByChainId(address, chainId) {
1122
- // TOOD: add rsk network checks.
1123
- if (!isAddressByChainId(address)) return address;
1124
- return toChecksumAddress(address);
1068
+
1069
+ /**
1070
+ * Determines if the maxFeePerGas and maxPriorityFeePerGas fields are supplied
1071
+ * and valid inputs. This will return false for non hex string inputs.
1072
+ * the transaction to check
1073
+ * @returns true if transaction uses valid EIP1559 fields
1074
+ */
1075
+ function isEIP1559Transaction(transaction) {
1076
+ var _transaction$transact, _transaction$transact2;
1077
+ return isHexString(addHexPrefix(transaction === null || transaction === void 0 || (_transaction$transact = transaction.transaction) === null || _transaction$transact === void 0 ? void 0 : _transaction$transact.maxFeePerGas)) && isHexString(addHexPrefix(transaction === null || transaction === void 0 || (_transaction$transact2 = transaction.transaction) === null || _transaction$transact2 === void 0 ? void 0 : _transaction$transact2.maxPriorityFeePerGas));
1125
1078
  }
1126
- const GAS_LIMITS = {
1127
- // maximum gasLimit of a simple send
1128
- SIMPLE: addHexPrefix(21000 .toString(16)),
1129
- // a base estimate for token transfers.
1130
- BASE_TOKEN_ESTIMATE: addHexPrefix(100000 .toString(16))
1131
- };
1132
- function bnLessThan(a, b) {
1133
- if (a === null || a === undefined || b === null || b === undefined) {
1134
- return null;
1135
- }
1136
- return new BigNumber(a, 10).lt(b, 10);
1079
+
1080
+ /**
1081
+ * Determine if the maxFeePerGas and maxPriorityFeePerGas fields are not
1082
+ * supplied and that the gasPrice field is valid if it is provided. This will
1083
+ * return false if gasPrice is a non hex string.
1084
+ * transaction -
1085
+ * the transaction to check
1086
+ * @returns true if transaction uses valid Legacy fields OR lacks
1087
+ * EIP1559 fields
1088
+ */
1089
+ function isLegacyTransaction(transaction) {
1090
+ return typeof transaction.transaction.maxFeePerGas === "undefined" && typeof transaction.transaction.maxPriorityFeePerGas === "undefined" && (typeof transaction.transaction.gasPrice === "undefined" || isHexString(addHexPrefix(transaction.transaction.gasPrice)));
1137
1091
  }
1138
- const getIpfsEndpoint = path => `https://infura-ipfs.io/${path}`;
1139
- function sanitizeNftMetdataUrl(url) {
1140
- let finalUri = url;
1141
- if (url !== null && url !== void 0 && url.startsWith("ipfs")) {
1142
- const ipfsPath = url.split("ipfs://")[1];
1143
- finalUri = getIpfsEndpoint(ipfsPath);
1092
+
1093
+ /**
1094
+ * Given two fields, ensure that the second field is not included in txParams,
1095
+ * and if it is throw an invalidParams error.
1096
+ */
1097
+ function ensureMutuallyExclusiveFieldsNotProvided(txParams, fieldBeingValidated, mutuallyExclusiveField) {
1098
+ if (typeof txParams[mutuallyExclusiveField] !== "undefined") {
1099
+ throw rpcErrors.invalidParams(`Invalid transaction params: specified ${fieldBeingValidated} but also included ${mutuallyExclusiveField}, these cannot be mixed`);
1144
1100
  }
1145
- return finalUri;
1146
1101
  }
1147
- function getChainType(chainId) {
1148
- if (chainId === MAINNET_CHAIN_ID) {
1149
- return "mainnet";
1150
- } else if (TEST_CHAINS.includes(chainId)) {
1151
- return "testnet";
1102
+
1103
+ /**
1104
+ * Ensures that the provided value for field is a string, throws an
1105
+ * invalidParams error if field is not a string.
1106
+ */
1107
+ function ensureFieldIsString(txParams, field) {
1108
+ if (typeof txParams[field] !== "string") {
1109
+ throw rpcErrors.invalidParams(`Invalid transaction params: ${field} is not a string. got: (${txParams[field]})`);
1152
1110
  }
1153
- return "custom";
1154
1111
  }
1155
1112
 
1156
- const DEFAULT_POLLING_INTERVAL = 20;
1157
- const DEFAULT_RETRY_TIMEOUT = 2;
1158
- const SEC = 1000;
1159
- class PollingBlockTracker extends BaseBlockTracker {
1160
- constructor(_ref) {
1161
- let {
1162
- config,
1163
- state = {}
1164
- } = _ref;
1165
- if (!config.provider) {
1166
- throw new Error("PollingBlockTracker - no provider specified.");
1167
- }
1168
- super({
1169
- config,
1170
- state
1171
- });
1172
- const pollingInterval = config.pollingInterval || DEFAULT_POLLING_INTERVAL;
1173
- const retryTimeout = config.retryTimeout || DEFAULT_RETRY_TIMEOUT;
1174
-
1175
- // merge default + provided config.
1176
- this.defaultConfig = {
1177
- provider: config.provider,
1178
- pollingInterval: pollingInterval * SEC,
1179
- retryTimeout: retryTimeout * SEC,
1180
- setSkipCacheFlag: config.setSkipCacheFlag || false
1181
- };
1182
- this.initialize();
1183
- }
1184
- async checkForLatestBlock() {
1185
- await this._updateLatestBlock();
1186
- return this.getLatestBlock();
1113
+ /**
1114
+ * Ensures that the provided txParams has the proper 'type' specified for the
1115
+ * given field, if it is provided. If types do not match throws an
1116
+ * invalidParams error.
1117
+ */
1118
+ function ensureProperTransactionEnvelopeTypeProvided(txParams, field) {
1119
+ switch (field) {
1120
+ case "maxFeePerGas":
1121
+ case "maxPriorityFeePerGas":
1122
+ if (txParams.type && txParams.type !== TRANSACTION_ENVELOPE_TYPES.FEE_MARKET) {
1123
+ throw rpcErrors.invalidParams(`Invalid transaction envelope type: specified type "${txParams.type}" but ` + `including maxFeePerGas and maxPriorityFeePerGas requires type: "${TRANSACTION_ENVELOPE_TYPES.FEE_MARKET}"`);
1124
+ }
1125
+ break;
1126
+ case "gasPrice":
1127
+ default:
1128
+ if (txParams.type && txParams.type === TRANSACTION_ENVELOPE_TYPES.FEE_MARKET) {
1129
+ throw rpcErrors.invalidParams(`Invalid transaction envelope type: specified type "${txParams.type}" but ` + "included a gasPrice instead of maxFeePerGas and maxPriorityFeePerGas");
1130
+ }
1187
1131
  }
1132
+ }
1188
1133
 
1189
- // overrides the BaseBlockTracker._start method.
1190
- _start() {
1191
- this._synchronize().catch(err => this.emit("error", err));
1192
- }
1193
- async _synchronize() {
1194
- while (this.state._isRunning) {
1195
- if (idleTimeTracker.checkIfIdle()) return;
1196
- try {
1197
- await this._updateLatestBlock();
1198
- await timeout(this.config.pollingInterval);
1199
- } catch (err) {
1200
- const newErr = new Error(`PollingBlockTracker - encountered an error while attempting to update latest block:\n${err.stack}`);
1201
- try {
1202
- this.emit("error", newErr);
1203
- } catch (emitErr) {
1204
- log.error(newErr);
1205
- }
1206
- await timeout(this.config.retryTimeout);
1207
- }
1208
- }
1209
- }
1210
- async _updateLatestBlock() {
1211
- // fetch + set latest block
1212
- const latestBlock = await this._fetchLatestBlock();
1213
- this._newPotentialLatest(latestBlock);
1134
+ /**
1135
+ * validates the from field in txParams
1136
+ */
1137
+ function validateFrom(txParams) {
1138
+ if (!(typeof txParams.from === "string")) {
1139
+ throw rpcErrors.invalidParams(`Invalid "from" address "${txParams.from}": not a string.`);
1214
1140
  }
1215
- async _fetchLatestBlock() {
1216
- try {
1217
- const block = await this.config.provider.request({
1218
- method: "eth_getBlockByNumber",
1219
- params: ["latest", false]
1220
- });
1221
- return {
1222
- blockHash: block.hash,
1223
- idempotencyKey: block.number,
1224
- timestamp: block.timestamp,
1225
- baseFeePerGas: block.baseFeePerGas,
1226
- gasLimit: block.gasLimit
1227
- };
1228
- } catch (error) {
1229
- log.error("Polling Block Tracker: ", error);
1230
- throw new Error(`PollingBlockTracker - encountered error fetching block:\n${error.message}`);
1231
- }
1141
+ if (!isValidAddress(txParams.from)) {
1142
+ throw rpcErrors.invalidParams('Invalid "from" address.');
1232
1143
  }
1233
1144
  }
1234
1145
 
1235
- class CurrencyController extends BaseCurrencyController {
1236
- constructor(_ref) {
1237
- let {
1238
- config,
1239
- state,
1240
- onNetworkChanged
1241
- } = _ref;
1242
- super({
1243
- config,
1244
- state
1245
- });
1246
- _defineProperty(this, "conversionInterval", void 0);
1247
- this.defaultState = _objectSpread(_objectSpread({}, this.defaultState), {}, {
1248
- commonDenomination: "USD",
1249
- commonDenominatorPrice: 0
1250
- });
1251
- this.initialize();
1252
- onNetworkChanged(networkState => {
1253
- // to be called as (listener) => this.networkController.on('networkDidChange', listener);
1254
- if (networkState.providerConfig.ticker.toUpperCase() !== this.state.nativeCurrency.toUpperCase()) {
1255
- this.setNativeCurrency(networkState.providerConfig.ticker);
1256
- this.updateConversionRate();
1257
- }
1258
- });
1259
- }
1260
- setCommonDenomination(commonDenomination) {
1261
- this.update({
1262
- commonDenomination
1263
- });
1264
- }
1265
- getCommonDenomination() {
1266
- return this.state.commonDenomination;
1267
- }
1268
- setCommonDenominatorPrice(commonDenominatorPrice) {
1269
- this.update({
1270
- commonDenominatorPrice
1271
- });
1272
- }
1273
- getCommonDenominatorPrice() {
1274
- return this.state.commonDenominatorPrice;
1275
- }
1276
-
1277
- /**
1278
- * Creates a new poll, using setInterval, to periodically call updateConversionRate. The id of the interval is
1279
- * stored at the controller's conversionInterval property. If it is called and such an id already exists, the
1280
- * previous interval is clear and a new one is created.
1281
- */
1282
- scheduleConversionInterval() {
1283
- if (this.conversionInterval) {
1284
- window.clearInterval(this.conversionInterval);
1285
- }
1286
- this.conversionInterval = window.setInterval(() => {
1287
- if (!idleTimeTracker.checkIfIdle()) {
1288
- this.updateConversionRate();
1289
- }
1290
- }, this.config.pollInterval);
1291
- }
1292
-
1293
- /**
1294
- * Updates the conversionRate and conversionDate properties associated with the currentCurrency. Updated info is
1295
- * fetched from an external API
1296
- */
1297
- async updateConversionRate() {
1298
- const currentCurrency = this.getCurrentCurrency();
1299
- const nativeCurrency = this.getNativeCurrency();
1300
- const commonDenomination = this.getCommonDenomination();
1301
- const conversionRate = await this.retrieveConversionRate(nativeCurrency, currentCurrency, commonDenomination);
1302
- const currentCurrencyRate = Number.parseFloat(conversionRate[currentCurrency.toUpperCase()]);
1303
- const commonDenominationRate = Number.parseFloat(conversionRate[commonDenomination.toUpperCase()]);
1304
- // set conversion rate
1305
- if (currentCurrencyRate || commonDenominationRate) {
1306
- // ETC
1307
- this.setConversionRate(currentCurrencyRate);
1308
- this.setConversionDate(Math.floor(Date.now() / 1000).toString());
1309
- if (currentCurrency.toUpperCase() === commonDenomination.toUpperCase()) {
1310
- this.setCommonDenominatorPrice(currentCurrencyRate);
1311
- } else {
1312
- this.setCommonDenominatorPrice(commonDenominationRate);
1313
- }
1146
+ /**
1147
+ * validates the to field in txParams
1148
+ */
1149
+ function validateRecipient(txParameters) {
1150
+ if (txParameters.to === "0x" || txParameters.to === null) {
1151
+ if (txParameters.data) {
1152
+ delete txParameters.to;
1314
1153
  } else {
1315
- this.setConversionRate(0);
1316
- this.setConversionDate("N/A");
1317
- }
1318
- }
1319
- async retrieveConversionRate(fromCurrency, toCurrency, commonDenomination) {
1320
- try {
1321
- // query cryptocompare
1322
- let apiUrl = `${this.config.api}/currency?fsym=${fromCurrency.toUpperCase()}&tsyms=${toCurrency.toUpperCase()}`;
1323
- if (commonDenomination && commonDenomination.toUpperCase() !== toCurrency.toUpperCase()) {
1324
- apiUrl += `,${commonDenomination.toUpperCase()}`;
1325
- }
1326
- const parsedResponse = await get(apiUrl);
1327
- return parsedResponse;
1328
- } catch (error) {
1329
- log.error(error, `CurrencyController - updateCommonDenominatorPrice: Failed to query rate for currency: ${fromCurrency}/ ${toCurrency}`);
1154
+ throw rpcErrors.invalidParams('Invalid "to" address.');
1330
1155
  }
1331
- return {
1332
- [toCurrency.toUpperCase()]: "0",
1333
- [commonDenomination.toUpperCase()]: "0"
1334
- };
1156
+ } else if (txParameters.to !== undefined && !isValidAddress(txParameters.to)) {
1157
+ throw rpcErrors.invalidParams('Invalid "to" address.');
1335
1158
  }
1159
+ return txParameters;
1336
1160
  }
1337
1161
 
1338
- const _excluded$1 = ["aBase", "bBase"],
1339
- _excluded2 = ["aBase", "bBase"],
1340
- _excluded3 = ["multiplicandBase", "multiplierBase"];
1341
-
1342
- // Big Number Constants
1343
- const BIG_NUMBER_WEI_MULTIPLIER = new BigNumber("1000000000000000000");
1344
- const BIG_NUMBER_GWEI_MULTIPLIER = new BigNumber("1000000000");
1345
- const BIG_NUMBER_ETH_MULTIPLIER = new BigNumber("1");
1346
- // Setter Maps
1347
- const toBigNumber = {
1348
- hex: n => new BigNumber(stripHexPrefix(n), 16),
1349
- dec: n => new BigNumber(String(n), 10),
1350
- BN: n => new BigNumber(n.toString(16), 16)
1351
- };
1352
- const toNormalizedDenomination = {
1353
- WEI: bigNumber => bigNumber.div(BIG_NUMBER_WEI_MULTIPLIER),
1354
- GWEI: bigNumber => bigNumber.div(BIG_NUMBER_GWEI_MULTIPLIER),
1355
- ETH: bigNumber => bigNumber.div(BIG_NUMBER_ETH_MULTIPLIER)
1356
- };
1357
- const toSpecifiedDenomination = {
1358
- WEI: bigNumber => bigNumber.times(BIG_NUMBER_WEI_MULTIPLIER).dp(0, BigNumber.ROUND_HALF_UP),
1359
- GWEI: bigNumber => bigNumber.times(BIG_NUMBER_GWEI_MULTIPLIER).dp(9, BigNumber.ROUND_HALF_UP),
1360
- ETH: bigNumber => bigNumber.times(BIG_NUMBER_ETH_MULTIPLIER).dp(9, BigNumber.ROUND_HALF_UP)
1361
- };
1362
- const baseChange = {
1363
- hex: n => n.toString(16),
1364
- dec: n => new BigNumber(n).toString(10),
1365
- BN: n => new BN(n.toString(16))
1366
- };
1367
-
1368
- // Utility function for checking base types
1369
- const isValidBase = base => Number.isInteger(base) && base > 1;
1370
-
1371
1162
  /**
1372
- * Utility method to convert a value between denominations, formats and currencies.
1163
+ * Validates the given tx parameters
1164
+ * @throws if the tx params contains invalid fields
1373
1165
  */
1374
- const converter = _ref => {
1375
- let {
1376
- value,
1377
- fromNumericBase,
1378
- fromDenomination,
1379
- fromCurrency,
1380
- toNumericBase,
1381
- toDenomination,
1382
- toCurrency,
1383
- numberOfDecimals,
1384
- conversionRate,
1385
- invertConversionRate,
1386
- roundDown
1387
- } = _ref;
1388
- let convertedValue = fromNumericBase ? toBigNumber[fromNumericBase](value) : value;
1389
- if (fromDenomination) {
1390
- convertedValue = toNormalizedDenomination[fromDenomination](convertedValue);
1391
- }
1392
- if (fromCurrency !== toCurrency) {
1393
- if (conversionRate === null || conversionRate === undefined) {
1394
- throw new Error(`Converting from ${fromCurrency} to ${toCurrency} requires a conversionRate, but one was not provided`);
1395
- }
1396
- let rate = toBigNumber.dec(conversionRate);
1397
- if (invertConversionRate) {
1398
- rate = new BigNumber(1).div(conversionRate);
1399
- }
1400
- convertedValue = convertedValue.times(rate);
1401
- }
1402
- if (toDenomination) {
1403
- convertedValue = toSpecifiedDenomination[toDenomination](convertedValue);
1404
- }
1405
- if (numberOfDecimals) {
1406
- convertedValue = convertedValue.dp(numberOfDecimals, BigNumber.ROUND_HALF_DOWN);
1407
- }
1408
- if (roundDown) {
1409
- convertedValue = convertedValue.dp(roundDown, BigNumber.ROUND_DOWN);
1166
+ function validateTxParameters(txParams) {
1167
+ let eip1559Compatibility = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
1168
+ if (!txParams || typeof txParams !== "object" || Array.isArray(txParams)) {
1169
+ throw rpcErrors.invalidParams("Invalid transaction params: must be an object.");
1410
1170
  }
1411
- if (toNumericBase) {
1412
- convertedValue = baseChange[toNumericBase](convertedValue);
1171
+ if (!txParams.to && !txParams.data) {
1172
+ throw rpcErrors.invalidParams('Invalid transaction params: must specify "data" for contract deployments, or "to" (and optionally "data") for all other types of transactions.');
1413
1173
  }
1414
- return convertedValue;
1415
- };
1416
- const conversionUtil = (value, _ref2) => {
1417
- let {
1418
- fromCurrency = null,
1419
- toCurrency = fromCurrency,
1420
- fromNumericBase,
1421
- toNumericBase,
1422
- fromDenomination,
1423
- toDenomination,
1424
- numberOfDecimals,
1425
- conversionRate,
1426
- invertConversionRate
1427
- } = _ref2;
1428
- if (fromCurrency !== toCurrency && !conversionRate) {
1429
- return 0;
1174
+ if (isEIP1559Transaction({
1175
+ transaction: txParams
1176
+ }) && !eip1559Compatibility) {
1177
+ throw rpcErrors.invalidParams("Invalid transaction params: params specify an EIP-1559 transaction but the current network does not support EIP-1559");
1430
1178
  }
1431
- return converter({
1432
- fromCurrency,
1433
- toCurrency,
1179
+ Object.entries(txParams).forEach(_ref => {
1180
+ let [key, value] = _ref;
1181
+ // validate types
1182
+ switch (key) {
1183
+ case "from":
1184
+ validateFrom(txParams);
1185
+ break;
1186
+ case "to":
1187
+ validateRecipient(txParams);
1188
+ break;
1189
+ case "gasPrice":
1190
+ ensureProperTransactionEnvelopeTypeProvided(txParams, "gasPrice");
1191
+ ensureMutuallyExclusiveFieldsNotProvided(txParams, "gasPrice", "maxFeePerGas");
1192
+ ensureMutuallyExclusiveFieldsNotProvided(txParams, "gasPrice", "maxPriorityFeePerGas");
1193
+ ensureFieldIsString(txParams, "gasPrice");
1194
+ break;
1195
+ case "maxFeePerGas":
1196
+ ensureProperTransactionEnvelopeTypeProvided(txParams, "maxFeePerGas");
1197
+ ensureMutuallyExclusiveFieldsNotProvided(txParams, "maxFeePerGas", "gasPrice");
1198
+ ensureFieldIsString(txParams, "maxFeePerGas");
1199
+ break;
1200
+ case "maxPriorityFeePerGas":
1201
+ ensureProperTransactionEnvelopeTypeProvided(txParams, "maxPriorityFeePerGas");
1202
+ ensureMutuallyExclusiveFieldsNotProvided(txParams, "maxPriorityFeePerGas", "gasPrice");
1203
+ ensureFieldIsString(txParams, "maxPriorityFeePerGas");
1204
+ break;
1205
+ case "value":
1206
+ ensureFieldIsString(txParams, "value");
1207
+ if (value.toString().includes("-")) {
1208
+ throw rpcErrors.invalidParams(`Invalid transaction value "${value}": not a positive number.`);
1209
+ }
1210
+ if (value.toString().includes(".")) {
1211
+ throw rpcErrors.invalidParams(`Invalid transaction value of "${value}": number must be in wei.`);
1212
+ }
1213
+ break;
1214
+ case "chainId":
1215
+ if (typeof value !== "number" && typeof value !== "string") {
1216
+ throw rpcErrors.invalidParams(`Invalid transaction params: ${key} is not a Number or hex string. got: (${value})`);
1217
+ }
1218
+ break;
1219
+ default:
1220
+ ensureFieldIsString(txParams, key);
1221
+ }
1222
+ });
1223
+ }
1224
+ function normalizeAndValidateTxParams(txParams) {
1225
+ let lowerCase = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
1226
+ const normalizedTxParams = normalizeTxParameters(txParams, lowerCase);
1227
+ validateTxParameters(normalizedTxParams);
1228
+ return normalizedTxParams;
1229
+ }
1230
+
1231
+ /**
1232
+ * @returns an array of states that can be considered final
1233
+ */
1234
+ function getFinalStates() {
1235
+ return [TransactionStatus.rejected,
1236
+ // the user has responded no!
1237
+ TransactionStatus.confirmed,
1238
+ // the tx has been included in a block.
1239
+ TransactionStatus.failed,
1240
+ // the tx failed for some reason, included on tx data.
1241
+ TransactionStatus.dropped // the tx nonce was already used
1242
+ ];
1243
+ }
1244
+ function parseStandardTokenTransactionData(data) {
1245
+ try {
1246
+ const txDesc = erc20Interface.parseTransaction({
1247
+ data
1248
+ });
1249
+ if (txDesc) return {
1250
+ name: txDesc.name,
1251
+ methodParams: txDesc.args.toArray(),
1252
+ type: CONTRACT_TYPE_ERC20
1253
+ };
1254
+ } catch {
1255
+ // ignore and next try to parse with erc721 ABI
1256
+ }
1257
+ try {
1258
+ const txDesc = erc721Interface.parseTransaction({
1259
+ data
1260
+ });
1261
+ if (txDesc) return {
1262
+ name: txDesc.name,
1263
+ methodParams: txDesc.args.toArray(),
1264
+ type: CONTRACT_TYPE_ERC721
1265
+ };
1266
+ } catch {
1267
+ // ignore and next try to parse with erc1155 ABI
1268
+ }
1269
+ try {
1270
+ const txDesc = erc1155Interface.parseTransaction({
1271
+ data
1272
+ });
1273
+ if (txDesc) return {
1274
+ name: txDesc.name,
1275
+ methodParams: txDesc.args.toArray(),
1276
+ type: CONTRACT_TYPE_ERC1155
1277
+ };
1278
+ } catch {
1279
+ // ignore and return undefined
1280
+ }
1281
+ return undefined;
1282
+ }
1283
+ const readAddressAsContract = async (provider, address) => {
1284
+ let contractCode;
1285
+ try {
1286
+ contractCode = await provider.request({
1287
+ method: METHOD_TYPES.ETH_GET_CODE,
1288
+ params: [address, "latest"]
1289
+ });
1290
+ } catch (e) {
1291
+ contractCode = null;
1292
+ }
1293
+ const isContractAddress = contractCode ? contractCode !== "0x" && contractCode !== "0x0" : false;
1294
+ return {
1295
+ contractCode,
1296
+ isContractAddress
1297
+ };
1298
+ };
1299
+ async function determineTransactionType(txParams, provider) {
1300
+ const {
1301
+ data,
1302
+ to
1303
+ } = txParams;
1304
+ let name = "";
1305
+ let methodParams = [];
1306
+ let type = "";
1307
+ try {
1308
+ ({
1309
+ name,
1310
+ methodParams,
1311
+ type
1312
+ } = data && parseStandardTokenTransactionData(data) || {});
1313
+ } catch (error) {
1314
+ log.debug("Failed to parse transaction data", error);
1315
+ }
1316
+ let result;
1317
+ let contractCode = "";
1318
+ if (data && !to) {
1319
+ result = TRANSACTION_TYPES.DEPLOY_CONTRACT;
1320
+ } else {
1321
+ const {
1322
+ contractCode: resultCode,
1323
+ isContractAddress
1324
+ } = await readAddressAsContract(provider, to);
1325
+ contractCode = resultCode;
1326
+ if (isContractAddress) {
1327
+ const valueExists = txParams.value && Number(txParams.value) !== 0;
1328
+ const tokenMethodName = [TRANSACTION_TYPES.TOKEN_METHOD_APPROVE, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM, TRANSACTION_TYPES.COLLECTIBLE_METHOD_SAFE_TRANSFER_FROM, TRANSACTION_TYPES.SET_APPROVAL_FOR_ALL].find(x => {
1329
+ var _name;
1330
+ return x.toLowerCase() === ((_name = name) === null || _name === void 0 ? void 0 : _name.toLowerCase());
1331
+ });
1332
+ result = data && tokenMethodName && !valueExists ? tokenMethodName : TRANSACTION_TYPES.CONTRACT_INTERACTION;
1333
+ } else {
1334
+ result = TRANSACTION_TYPES.SENT_ETHER;
1335
+ }
1336
+ }
1337
+ return {
1338
+ type: type || CONTRACT_TYPE_ETH,
1339
+ category: result,
1340
+ methodParams,
1341
+ getCodeResponse: contractCode
1342
+ };
1343
+ }
1344
+
1345
+ function getEtherScanHashLink(txHash, chainId) {
1346
+ if (!SUPPORTED_NETWORKS[chainId]) return "";
1347
+ return `${SUPPORTED_NETWORKS[chainId].blockExplorerUrl}/tx/${txHash}`;
1348
+ }
1349
+ const formatPastTx = (x, lowerCaseSelectedAddress) => {
1350
+ var _x$to;
1351
+ let totalAmountString = "";
1352
+ if (x.type === CONTRACT_TYPE_ERC721 || x.type === CONTRACT_TYPE_ERC1155) totalAmountString = x.symbol;else if (x.type === CONTRACT_TYPE_ERC20) totalAmountString = formatSmallNumbers(Number.parseFloat(x.total_amount), x.symbol, true);else totalAmountString = formatSmallNumbers(Number.parseFloat(x.total_amount), x.type_name, true);
1353
+ const currencyAmountString = x.type === CONTRACT_TYPE_ERC721 || x.type === CONTRACT_TYPE_ERC1155 ? "" : formatSmallNumbers(Number.parseFloat(x.currency_amount), x.selected_currency, true);
1354
+ const finalObject = {
1355
+ id: x.created_at.toString(),
1356
+ date: new Date(x.created_at).toString(),
1357
+ from: x.from,
1358
+ from_aa_address: x.from_aa_address,
1359
+ slicedFrom: typeof x.from === "string" ? addressSlicer(x.from) : "",
1360
+ to: x.to,
1361
+ slicedTo: typeof x.to === "string" ? addressSlicer(x.to) : "",
1362
+ action: lowerCaseSelectedAddress === ((_x$to = x.to) === null || _x$to === void 0 ? void 0 : _x$to.toLowerCase()) || "" ? ACTIVITY_ACTION_RECEIVE : ACTIVITY_ACTION_SEND,
1363
+ totalAmount: x.total_amount,
1364
+ totalAmountString,
1365
+ currencyAmount: x.currency_amount,
1366
+ currencyAmountString,
1367
+ amount: `${totalAmountString} / ${currencyAmountString}`,
1368
+ status: x.status,
1369
+ etherscanLink: getEtherScanHashLink(x.transaction_hash, x.chain_id || MAINNET_CHAIN_ID),
1370
+ chainId: x.chain_id,
1371
+ ethRate: Number.parseFloat(x === null || x === void 0 ? void 0 : x.total_amount) && Number.parseFloat(x === null || x === void 0 ? void 0 : x.currency_amount) ? `1 ${x.symbol} = ${significantDigits(Number.parseFloat(x.currency_amount) / Number.parseFloat(x.total_amount))}` : "",
1372
+ currencyUsed: x.selected_currency,
1373
+ type: x.type,
1374
+ type_name: x.type_name,
1375
+ type_image_link: x.type_image_link,
1376
+ transaction_hash: x.transaction_hash,
1377
+ transaction_category: x.transaction_category,
1378
+ // TODO: // figure out how to handle these values.
1379
+ isEtherscan: x.isEtherscan,
1380
+ input: x.input || "",
1381
+ token_id: x.token_id || "",
1382
+ contract_address: x.contract_address || "",
1383
+ nonce: x.nonce || "",
1384
+ is_cancel: !!x.is_cancel || false,
1385
+ gas: x.gas || "",
1386
+ gasPrice: x.gasPrice || ""
1387
+ };
1388
+ return finalObject;
1389
+ };
1390
+
1391
+ /**
1392
+ * Ref - https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionreceipt
1393
+ */
1394
+ const getEthTxStatus = async (hash, provider) => {
1395
+ try {
1396
+ const result = await provider.request({
1397
+ method: METHOD_TYPES.ETH_GET_TRANSACTION_RECEIPT,
1398
+ params: [hash]
1399
+ });
1400
+ if (result === null) return TransactionStatus.submitted;
1401
+ if (result && result.status === "0x1") return TransactionStatus.confirmed;
1402
+ if (result && result.status === "0x0") return TransactionStatus.rejected;
1403
+ return undefined;
1404
+ } catch (err) {
1405
+ log.warn("unable to fetch transaction status", err);
1406
+ return undefined;
1407
+ }
1408
+ };
1409
+ function formatDate(inputDate) {
1410
+ const monthList = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1411
+ const date = new Date(inputDate);
1412
+ const day = date.getDate();
1413
+ const month = monthList[date.getMonth()];
1414
+ const year = date.getFullYear();
1415
+ return `${day} ${month} ${year}`;
1416
+ }
1417
+ function formatTime(time) {
1418
+ return new Date(time).toTimeString().slice(0, 8);
1419
+ }
1420
+ const idleTimeTracker = (activityThresholdTime => {
1421
+ let isIdle = false;
1422
+ let idleTimeout = null;
1423
+ const resetTimer = () => {
1424
+ if (idleTimeout) {
1425
+ window.clearTimeout(idleTimeout);
1426
+ }
1427
+ isIdle = false;
1428
+ idleTimeout = window.setTimeout(() => {
1429
+ isIdle = true;
1430
+ }, activityThresholdTime * 1000);
1431
+ };
1432
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
1433
+ window.addEventListener("load", resetTimer);
1434
+ document.addEventListener("mousemove", resetTimer);
1435
+ document.addEventListener("keydown", resetTimer);
1436
+ }
1437
+ function checkIfIdle() {
1438
+ return isIdle;
1439
+ }
1440
+ return {
1441
+ checkIfIdle
1442
+ };
1443
+ })(60 * 3);
1444
+ function isAddressByChainId(address, _chainId) {
1445
+ // TOOD: add rsk network checks.
1446
+ return isValidAddress(address);
1447
+ }
1448
+ function toChecksumAddressByChainId(address, chainId) {
1449
+ // TOOD: add rsk network checks.
1450
+ if (!isAddressByChainId(address)) return address;
1451
+ return toChecksumAddress(address);
1452
+ }
1453
+ const GAS_LIMITS = {
1454
+ // maximum gasLimit of a simple send
1455
+ SIMPLE: addHexPrefix(21000 .toString(16)),
1456
+ // a base estimate for token transfers.
1457
+ BASE_TOKEN_ESTIMATE: addHexPrefix(100000 .toString(16))
1458
+ };
1459
+ function bnLessThan(a, b) {
1460
+ if (a === null || a === undefined || b === null || b === undefined) {
1461
+ return null;
1462
+ }
1463
+ return new BigNumber(a, 10).lt(b, 10);
1464
+ }
1465
+ const getIpfsEndpoint = path => `https://infura-ipfs.io/${path}`;
1466
+ function sanitizeNftMetdataUrl(url) {
1467
+ let finalUri = url;
1468
+ if (url !== null && url !== void 0 && url.startsWith("ipfs")) {
1469
+ const ipfsPath = url.split("ipfs://")[1];
1470
+ finalUri = getIpfsEndpoint(ipfsPath);
1471
+ }
1472
+ return finalUri;
1473
+ }
1474
+ function getChainType(chainId) {
1475
+ if (chainId === MAINNET_CHAIN_ID) {
1476
+ return "mainnet";
1477
+ } else if (TEST_CHAINS.includes(chainId)) {
1478
+ return "testnet";
1479
+ }
1480
+ return "custom";
1481
+ }
1482
+ const addEtherscanTransactions = async (txn, lowerCaseSelectedAddress, provider, chainId) => {
1483
+ const transactionPromises = await Promise.all(txn.map(async tx => {
1484
+ var _SUPPORTED_NETWORKS$c, _SUPPORTED_NETWORKS$c2;
1485
+ const {
1486
+ category,
1487
+ type
1488
+ } = await determineTransactionType(_objectSpread(_objectSpread({}, tx), {}, {
1489
+ data: tx.input
1490
+ }), provider);
1491
+ tx.transaction_category = tx.transaction_category || category;
1492
+ tx.type_image_link = ((_SUPPORTED_NETWORKS$c = SUPPORTED_NETWORKS[chainId]) === null || _SUPPORTED_NETWORKS$c === void 0 ? void 0 : _SUPPORTED_NETWORKS$c.logo) || "";
1493
+ tx.type_name = (_SUPPORTED_NETWORKS$c2 = SUPPORTED_NETWORKS[chainId]) === null || _SUPPORTED_NETWORKS$c2 === void 0 ? void 0 : _SUPPORTED_NETWORKS$c2.ticker;
1494
+ tx.type = type;
1495
+ return tx;
1496
+ }));
1497
+ const finalTxs = transactionPromises.reduce((accumulator, x) => {
1498
+ var _SUPPORTED_NETWORKS$c3, _SUPPORTED_NETWORKS$c4;
1499
+ const totalAmount = x.value ? formatEther(x.value) : "";
1500
+ const etherscanTransaction = {
1501
+ etherscanLink: getEtherScanHashLink(x.hash, chainId),
1502
+ type: x.type || ((_SUPPORTED_NETWORKS$c3 = SUPPORTED_NETWORKS[chainId]) === null || _SUPPORTED_NETWORKS$c3 === void 0 ? void 0 : _SUPPORTED_NETWORKS$c3.ticker) || CONTRACT_TYPE_ETH,
1503
+ type_image_link: x.type_image_link || "n/a",
1504
+ type_name: x.type_name || "n/a",
1505
+ symbol: (_SUPPORTED_NETWORKS$c4 = SUPPORTED_NETWORKS[chainId]) === null || _SUPPORTED_NETWORKS$c4 === void 0 ? void 0 : _SUPPORTED_NETWORKS$c4.ticker,
1506
+ token_id: x.tokenID || "",
1507
+ total_amount: totalAmount,
1508
+ created_at: new Date(Number(x.timeStamp) * 1000),
1509
+ from: x.from,
1510
+ to: x.to,
1511
+ transaction_hash: x.hash,
1512
+ status: x.txreceipt_status && x.txreceipt_status === "0" ? TransactionStatus.failed : TransactionStatus.approved,
1513
+ isEtherscan: true,
1514
+ input: x.input,
1515
+ contract_address: x.contractAddress,
1516
+ transaction_category: x.transaction_category,
1517
+ gas: x.gas,
1518
+ gasPrice: x.gasPrice,
1519
+ chain_id: chainId,
1520
+ currency_amount: "",
1521
+ nonce: x.nonce,
1522
+ from_aa_address: "",
1523
+ is_cancel: false,
1524
+ selected_currency: ""
1525
+ };
1526
+ accumulator.push(formatPastTx(etherscanTransaction, lowerCaseSelectedAddress));
1527
+ return accumulator;
1528
+ }, []);
1529
+ return finalTxs;
1530
+ };
1531
+
1532
+ const DEFAULT_POLLING_INTERVAL = 20;
1533
+ const DEFAULT_RETRY_TIMEOUT = 2;
1534
+ const SEC = 1000;
1535
+ class PollingBlockTracker extends BaseBlockTracker {
1536
+ constructor(_ref) {
1537
+ let {
1538
+ config,
1539
+ state = {}
1540
+ } = _ref;
1541
+ if (!config.provider) {
1542
+ throw new Error("PollingBlockTracker - no provider specified.");
1543
+ }
1544
+ super({
1545
+ config,
1546
+ state
1547
+ });
1548
+ const pollingInterval = config.pollingInterval || DEFAULT_POLLING_INTERVAL;
1549
+ const retryTimeout = config.retryTimeout || DEFAULT_RETRY_TIMEOUT;
1550
+
1551
+ // merge default + provided config.
1552
+ this.defaultConfig = {
1553
+ provider: config.provider,
1554
+ pollingInterval: pollingInterval * SEC,
1555
+ retryTimeout: retryTimeout * SEC,
1556
+ setSkipCacheFlag: config.setSkipCacheFlag || false
1557
+ };
1558
+ this.initialize();
1559
+ }
1560
+ async checkForLatestBlock() {
1561
+ await this._updateLatestBlock();
1562
+ return this.getLatestBlock();
1563
+ }
1564
+
1565
+ // overrides the BaseBlockTracker._start method.
1566
+ _start() {
1567
+ this._synchronize().catch(err => this.emit("error", err));
1568
+ }
1569
+ async _synchronize() {
1570
+ while (this.state._isRunning) {
1571
+ if (idleTimeTracker.checkIfIdle()) return;
1572
+ try {
1573
+ await this._updateLatestBlock();
1574
+ await timeout(this.config.pollingInterval);
1575
+ } catch (err) {
1576
+ const newErr = new Error(`PollingBlockTracker - encountered an error while attempting to update latest block:\n${err.stack}`);
1577
+ try {
1578
+ this.emit("error", newErr);
1579
+ } catch (emitErr) {
1580
+ log.error(newErr);
1581
+ }
1582
+ await timeout(this.config.retryTimeout);
1583
+ }
1584
+ }
1585
+ }
1586
+ async _updateLatestBlock() {
1587
+ // fetch + set latest block
1588
+ const latestBlock = await this._fetchLatestBlock();
1589
+ this._newPotentialLatest(latestBlock);
1590
+ }
1591
+ async _fetchLatestBlock() {
1592
+ try {
1593
+ const block = await this.config.provider.request({
1594
+ method: "eth_getBlockByNumber",
1595
+ params: ["latest", false]
1596
+ });
1597
+ return {
1598
+ blockHash: block.hash,
1599
+ idempotencyKey: block.number,
1600
+ timestamp: block.timestamp,
1601
+ baseFeePerGas: block.baseFeePerGas,
1602
+ gasLimit: block.gasLimit
1603
+ };
1604
+ } catch (error) {
1605
+ log.error("Polling Block Tracker: ", error);
1606
+ throw new Error(`PollingBlockTracker - encountered error fetching block:\n${error.message}`);
1607
+ }
1608
+ }
1609
+ }
1610
+
1611
+ class CurrencyController extends BaseCurrencyController {
1612
+ constructor(_ref) {
1613
+ let {
1614
+ config,
1615
+ state,
1616
+ onNetworkChanged
1617
+ } = _ref;
1618
+ super({
1619
+ config,
1620
+ state
1621
+ });
1622
+ _defineProperty(this, "conversionInterval", void 0);
1623
+ this.defaultState = _objectSpread(_objectSpread({}, this.defaultState), {}, {
1624
+ commonDenomination: "USD",
1625
+ commonDenominatorPrice: 0
1626
+ });
1627
+ this.initialize();
1628
+ onNetworkChanged(networkState => {
1629
+ // to be called as (listener) => this.networkController.on('networkDidChange', listener);
1630
+ if (networkState.providerConfig.ticker.toUpperCase() !== this.state.nativeCurrency.toUpperCase()) {
1631
+ this.setNativeCurrency(networkState.providerConfig.ticker);
1632
+ this.updateConversionRate();
1633
+ }
1634
+ });
1635
+ }
1636
+ setCommonDenomination(commonDenomination) {
1637
+ this.update({
1638
+ commonDenomination
1639
+ });
1640
+ }
1641
+ getCommonDenomination() {
1642
+ return this.state.commonDenomination;
1643
+ }
1644
+ setCommonDenominatorPrice(commonDenominatorPrice) {
1645
+ this.update({
1646
+ commonDenominatorPrice
1647
+ });
1648
+ }
1649
+ getCommonDenominatorPrice() {
1650
+ return this.state.commonDenominatorPrice;
1651
+ }
1652
+
1653
+ /**
1654
+ * Creates a new poll, using setInterval, to periodically call updateConversionRate. The id of the interval is
1655
+ * stored at the controller's conversionInterval property. If it is called and such an id already exists, the
1656
+ * previous interval is clear and a new one is created.
1657
+ */
1658
+ scheduleConversionInterval() {
1659
+ if (this.conversionInterval) {
1660
+ window.clearInterval(this.conversionInterval);
1661
+ }
1662
+ this.conversionInterval = window.setInterval(() => {
1663
+ if (!idleTimeTracker.checkIfIdle()) {
1664
+ this.updateConversionRate();
1665
+ }
1666
+ }, this.config.pollInterval);
1667
+ }
1668
+
1669
+ /**
1670
+ * Updates the conversionRate and conversionDate properties associated with the currentCurrency. Updated info is
1671
+ * fetched from an external API
1672
+ */
1673
+ async updateConversionRate() {
1674
+ const currentCurrency = this.getCurrentCurrency();
1675
+ const nativeCurrency = this.getNativeCurrency();
1676
+ const commonDenomination = this.getCommonDenomination();
1677
+ const conversionRate = await this.retrieveConversionRate(nativeCurrency, currentCurrency, commonDenomination);
1678
+ const currentCurrencyRate = Number.parseFloat(conversionRate[currentCurrency.toUpperCase()]);
1679
+ const commonDenominationRate = Number.parseFloat(conversionRate[commonDenomination.toUpperCase()]);
1680
+ // set conversion rate
1681
+ if (currentCurrencyRate || commonDenominationRate) {
1682
+ // ETC
1683
+ this.setConversionRate(currentCurrencyRate);
1684
+ this.setConversionDate(Math.floor(Date.now() / 1000).toString());
1685
+ if (currentCurrency.toUpperCase() === commonDenomination.toUpperCase()) {
1686
+ this.setCommonDenominatorPrice(currentCurrencyRate);
1687
+ } else {
1688
+ this.setCommonDenominatorPrice(commonDenominationRate);
1689
+ }
1690
+ } else {
1691
+ this.setConversionRate(0);
1692
+ this.setConversionDate("N/A");
1693
+ }
1694
+ }
1695
+ async retrieveConversionRate(fromCurrency, toCurrency, commonDenomination) {
1696
+ try {
1697
+ // query cryptocompare
1698
+ let apiUrl = `${this.config.api}/currency?fsym=${fromCurrency.toUpperCase()}&tsyms=${toCurrency.toUpperCase()}`;
1699
+ if (commonDenomination && commonDenomination.toUpperCase() !== toCurrency.toUpperCase()) {
1700
+ apiUrl += `,${commonDenomination.toUpperCase()}`;
1701
+ }
1702
+ const parsedResponse = await get(apiUrl);
1703
+ return parsedResponse;
1704
+ } catch (error) {
1705
+ log.error(error, `CurrencyController - updateCommonDenominatorPrice: Failed to query rate for currency: ${fromCurrency}/ ${toCurrency}`);
1706
+ }
1707
+ return {
1708
+ [toCurrency.toUpperCase()]: "0",
1709
+ [commonDenomination.toUpperCase()]: "0"
1710
+ };
1711
+ }
1712
+ }
1713
+
1714
+ const _excluded$1 = ["aBase", "bBase"],
1715
+ _excluded2 = ["aBase", "bBase"],
1716
+ _excluded3 = ["multiplicandBase", "multiplierBase"];
1717
+
1718
+ // Big Number Constants
1719
+ const BIG_NUMBER_WEI_MULTIPLIER = new BigNumber("1000000000000000000");
1720
+ const BIG_NUMBER_GWEI_MULTIPLIER = new BigNumber("1000000000");
1721
+ const BIG_NUMBER_ETH_MULTIPLIER = new BigNumber("1");
1722
+ // Setter Maps
1723
+ const toBigNumber = {
1724
+ hex: n => new BigNumber(stripHexPrefix(n), 16),
1725
+ dec: n => new BigNumber(String(n), 10),
1726
+ BN: n => new BigNumber(n.toString(16), 16)
1727
+ };
1728
+ const toNormalizedDenomination = {
1729
+ WEI: bigNumber => bigNumber.div(BIG_NUMBER_WEI_MULTIPLIER),
1730
+ GWEI: bigNumber => bigNumber.div(BIG_NUMBER_GWEI_MULTIPLIER),
1731
+ ETH: bigNumber => bigNumber.div(BIG_NUMBER_ETH_MULTIPLIER)
1732
+ };
1733
+ const toSpecifiedDenomination = {
1734
+ WEI: bigNumber => bigNumber.times(BIG_NUMBER_WEI_MULTIPLIER).dp(0, BigNumber.ROUND_HALF_UP),
1735
+ GWEI: bigNumber => bigNumber.times(BIG_NUMBER_GWEI_MULTIPLIER).dp(9, BigNumber.ROUND_HALF_UP),
1736
+ ETH: bigNumber => bigNumber.times(BIG_NUMBER_ETH_MULTIPLIER).dp(9, BigNumber.ROUND_HALF_UP)
1737
+ };
1738
+ const baseChange = {
1739
+ hex: n => n.toString(16),
1740
+ dec: n => new BigNumber(n).toString(10),
1741
+ BN: n => new BN(n.toString(16))
1742
+ };
1743
+
1744
+ // Utility function for checking base types
1745
+ const isValidBase = base => Number.isInteger(base) && base > 1;
1746
+
1747
+ /**
1748
+ * Utility method to convert a value between denominations, formats and currencies.
1749
+ */
1750
+ const converter = _ref => {
1751
+ let {
1752
+ value,
1753
+ fromNumericBase,
1754
+ fromDenomination,
1755
+ fromCurrency,
1756
+ toNumericBase,
1757
+ toDenomination,
1758
+ toCurrency,
1759
+ numberOfDecimals,
1760
+ conversionRate,
1761
+ invertConversionRate,
1762
+ roundDown
1763
+ } = _ref;
1764
+ let convertedValue = fromNumericBase ? toBigNumber[fromNumericBase](value) : value;
1765
+ if (fromDenomination) {
1766
+ convertedValue = toNormalizedDenomination[fromDenomination](convertedValue);
1767
+ }
1768
+ if (fromCurrency !== toCurrency) {
1769
+ if (conversionRate === null || conversionRate === undefined) {
1770
+ throw new Error(`Converting from ${fromCurrency} to ${toCurrency} requires a conversionRate, but one was not provided`);
1771
+ }
1772
+ let rate = toBigNumber.dec(conversionRate);
1773
+ if (invertConversionRate) {
1774
+ rate = new BigNumber(1).div(conversionRate);
1775
+ }
1776
+ convertedValue = convertedValue.times(rate);
1777
+ }
1778
+ if (toDenomination) {
1779
+ convertedValue = toSpecifiedDenomination[toDenomination](convertedValue);
1780
+ }
1781
+ if (numberOfDecimals) {
1782
+ convertedValue = convertedValue.dp(numberOfDecimals, BigNumber.ROUND_HALF_DOWN);
1783
+ }
1784
+ if (roundDown) {
1785
+ convertedValue = convertedValue.dp(roundDown, BigNumber.ROUND_DOWN);
1786
+ }
1787
+ if (toNumericBase) {
1788
+ convertedValue = baseChange[toNumericBase](convertedValue);
1789
+ }
1790
+ return convertedValue;
1791
+ };
1792
+ const conversionUtil = (value, _ref2) => {
1793
+ let {
1794
+ fromCurrency = null,
1795
+ toCurrency = fromCurrency,
1796
+ fromNumericBase,
1797
+ toNumericBase,
1798
+ fromDenomination,
1799
+ toDenomination,
1800
+ numberOfDecimals,
1801
+ conversionRate,
1802
+ invertConversionRate
1803
+ } = _ref2;
1804
+ if (fromCurrency !== toCurrency && !conversionRate) {
1805
+ return 0;
1806
+ }
1807
+ return converter({
1808
+ fromCurrency,
1809
+ toCurrency,
1434
1810
  fromNumericBase,
1435
1811
  toNumericBase,
1436
1812
  fromDenomination,
@@ -3721,7 +4097,8 @@ class PreferencesController extends BasePreferencesController {
3721
4097
  defaultPreferences: {
3722
4098
  formattedPastTransactions: [],
3723
4099
  fetchedPastTx: [],
3724
- paymentTx: []
4100
+ paymentTx: [],
4101
+ etherscanTransactions: []
3725
4102
  },
3726
4103
  signAuthMessage
3727
4104
  });
@@ -3772,7 +4149,8 @@ class PreferencesController extends BasePreferencesController {
3772
4149
  type,
3773
4150
  metadata: {
3774
4151
  email: userInfo.email,
3775
- signatures
4152
+ signatures,
4153
+ network: web3AuthNetwork
3776
4154
  }
3777
4155
  });
3778
4156
  const {
@@ -3902,10 +4280,15 @@ class PreferencesController extends BasePreferencesController {
3902
4280
  chainId
3903
4281
  } = this.getProviderConfig();
3904
4282
  if (ETHERSCAN_SUPPORTED_CHAINS.includes(chainId)) {
3905
- return this.fetchEtherscanTx({
4283
+ const etherscanTxn = await this.fetchEtherscanTx({
3906
4284
  selectedAddress,
3907
4285
  chainId: this.getProviderConfig().chainId
3908
4286
  });
4287
+ const finalEthScanTxn = await addEtherscanTransactions(etherscanTxn, selectedAddress, this.provider, chainId);
4288
+ this.updateState({
4289
+ etherscanTransactions: finalEthScanTxn
4290
+ });
4291
+ return etherscanTxn;
3909
4292
  }
3910
4293
  }
3911
4294
  }
@@ -3914,6 +4297,7 @@ class PreferencesController extends BasePreferencesController {
3914
4297
  const url = new URL(`${this.config.api}/etherscan`);
3915
4298
  url.searchParams.append("chainId", parameters.chainId);
3916
4299
  const response = await get(url.href, this.headers(parameters.selectedAddress));
4300
+ log.info("Etherscan Response", response);
3917
4301
  return response.success ? response.data : [];
3918
4302
  } catch (error) {
3919
4303
  log.error("unable to fetch etherscan tx", error);
@@ -4624,712 +5008,385 @@ class NonceTracker {
4624
5008
  let mutex = this.lockMap[lockId];
4625
5009
  if (!mutex) {
4626
5010
  mutex = new Mutex();
4627
- this.lockMap[lockId] = mutex;
4628
- }
4629
- return mutex;
4630
- }
4631
- async _getNetworkNextNonce(address) {
4632
- // calculate next nonce
4633
- // we need to make sure our base count
4634
- // and pending count are from the same block
4635
- const block = await this.blockTracker.getLatestBlock();
4636
- const baseCountStr = await this.provider.request({
4637
- method: METHOD_TYPES.ETH_GET_TRANSACTION_COUNT,
4638
- params: [address, block.idempotencyKey]
4639
- });
4640
- const baseCount = Number.parseInt(baseCountStr, 16);
4641
- const nonceDetails = {
4642
- block,
4643
- baseCount
4644
- };
4645
- return {
4646
- name: "network",
4647
- nonce: baseCount,
4648
- details: nonceDetails
4649
- };
4650
- }
4651
- _getHighestLocallyConfirmed(address) {
4652
- const confirmedTransactions = this.getConfirmedTransactions(address);
4653
- const highest = this._getHighestNonce(confirmedTransactions);
4654
- return Number.isInteger(highest) ? highest + 1 : 0;
4655
- }
4656
- _getHighestNonce(txList) {
4657
- const nonces = txList.map(txMeta => {
4658
- const {
4659
- nonce
4660
- } = txMeta.transaction;
4661
- return Number.parseInt(nonce, 16);
4662
- });
4663
- const highestNonce = Math.max.apply(null, nonces);
4664
- return highestNonce;
4665
- }
4666
- _getHighestContinuousFrom(txList, startPoint) {
4667
- const nonces = new Set(txList.map(txMeta => {
4668
- const {
4669
- nonce
4670
- } = txMeta.transaction;
4671
- return Number.parseInt(nonce, 16);
4672
- }));
4673
- let highest = startPoint;
4674
- while (nonces.has(highest)) {
4675
- highest += 1;
4676
- }
4677
- return {
4678
- name: "local",
4679
- nonce: highest,
4680
- details: {
4681
- startPoint,
4682
- highest
4683
- }
4684
- };
4685
- }
4686
- }
4687
-
4688
- class PendingTransactionTracker extends SafeEventEmitter {
4689
- constructor(_ref) {
4690
- let {
4691
- provider,
4692
- nonceTracker,
4693
- approveTransaction,
4694
- publishTransaction,
4695
- getPendingTransactions,
4696
- getConfirmedTransactions
4697
- } = _ref;
4698
- super();
4699
- _defineProperty(this, "DROPPED_BUFFER_COUNT", 3);
4700
- _defineProperty(this, "nonceTracker", void 0);
4701
- _defineProperty(this, "provider", void 0);
4702
- _defineProperty(this, "approveTransaction", void 0);
4703
- _defineProperty(this, "droppedBlocksBufferByHash", void 0);
4704
- _defineProperty(this, "getConfirmedTransactions", void 0);
4705
- _defineProperty(this, "getPendingTransactions", void 0);
4706
- _defineProperty(this, "publishTransaction", void 0);
4707
- this.provider = provider;
4708
- this.nonceTracker = nonceTracker;
4709
- this.approveTransaction = approveTransaction;
4710
- this.publishTransaction = publishTransaction;
4711
- this.getPendingTransactions = getPendingTransactions;
4712
- this.getConfirmedTransactions = getConfirmedTransactions;
4713
- this.droppedBlocksBufferByHash = new Map();
4714
- }
4715
-
4716
- /**
4717
- checks the network for signed txs and releases the nonce global lock if it is
4718
- */
4719
- async updatePendingTxs() {
4720
- // in order to keep the nonceTracker accurate we block it while updating pending transactions
4721
- const nonceGlobalLock = await this.nonceTracker.getGlobalLock();
4722
- try {
4723
- const pendingTxs = this.getPendingTransactions();
4724
- await Promise.all(pendingTxs.map(txMeta => this._checkPendingTx(txMeta)));
4725
- } catch (error) {
4726
- log.error("PendingTransactionTracker - Error updating pending transactions");
4727
- log.error(error);
4728
- }
4729
- nonceGlobalLock.releaseLock();
4730
- }
4731
- async resubmitPendingTxs(block) {
4732
- const pending = this.getPendingTransactions();
4733
- // only try resubmitting if their are transactions to resubmit
4734
- if (pending.length === 0) return;
4735
- // Keep this as a for loop because we want to wait for each item to be submitted
4736
- for (const txMeta of pending) {
4737
- try {
4738
- await this._resubmitTx(txMeta, block.idempotencyKey);
4739
- } catch (error) {
4740
- var _value;
4741
- /*
4742
- Dont marked as failed if the error is a "known" transaction warning
4743
- "there is already a transaction with the same sender-nonce
4744
- but higher/same gas price"
4745
- Also don't mark as failed if it has ever been broadcast successfully.
4746
- A successful broadcast means it may still be mined.
4747
- */
4748
- const errorMessage = ((_value = error.value) === null || _value === void 0 || (_value = _value.message) === null || _value === void 0 ? void 0 : _value.toLowerCase()) || error.message.toLowerCase();
4749
- const isKnownTx =
4750
- // geth
4751
- errorMessage.includes("replacement transaction underpriced") || errorMessage.includes("known transaction") ||
4752
- // parity
4753
- errorMessage.includes("gas price too low to replace") || errorMessage.includes("transaction with the same hash was already imported") ||
4754
- // other
4755
- errorMessage.includes("gateway timeout") || errorMessage.includes("nonce too low");
4756
- // ignore resubmit warnings, return early
4757
- if (isKnownTx) return;
4758
- // encountered real error - transition to error state
4759
- txMeta.warning = {
4760
- error: errorMessage,
4761
- message: "There was an error when resubmitting this transaction."
4762
- };
4763
- this.emit(TX_EVENTS.TX_WARNING, {
4764
- txMeta,
4765
- error,
4766
- txId: txMeta.id
4767
- });
4768
- }
4769
- }
4770
- }
4771
- async _resubmitTx(txMeta, latestBlockNumber) {
4772
- if (!txMeta.firstRetryBlockNumber) {
4773
- this.emit(TX_EVENTS.TX_BLOCK_UPDATE, {
4774
- txMeta,
4775
- latestBlockNumber,
4776
- txId: txMeta.id
4777
- });
4778
- }
4779
- const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber;
4780
- const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16);
4781
- const retryCount = txMeta.retryCount || 0;
4782
-
4783
- // Exponential backoff to limit retries at publishing (capped at last 15 mins)
4784
- if (txBlockDistance <= Math.min(50, 2 ** retryCount)) return undefined;
4785
-
4786
- // Only auto-submit already-signed txs:
4787
- if (!("rawTx" in txMeta)) return this.approveTransaction(txMeta.id);
4788
- const {
4789
- rawTx
4790
- } = txMeta;
4791
- const txHash = await this.publishTransaction(rawTx);
4792
-
4793
- // Increment successful tries:
4794
- this.emit(TX_EVENTS.TX_RETRY, {
4795
- txMeta,
4796
- txId: txMeta.id
4797
- });
4798
- return txHash;
4799
- }
4800
- async _checkPendingTx(foundTx) {
4801
- const txMeta = foundTx;
4802
- const txHash = txMeta.transactionHash;
4803
- const txId = txMeta.id;
4804
-
4805
- // Only check submitted txs
4806
- if (txMeta.status !== TransactionStatus.submitted) return;
4807
-
4808
- // extra check in case there was an uncaught error during the
4809
- // signature and submission process
4810
- if (!txHash) {
4811
- const noTxHashError = new Error("We had an error while submitting this transaction, please try again.");
4812
- noTxHashError.name = "NoTxHashError";
4813
- this.emit(TX_EVENTS.TX_FAILED, {
4814
- txId,
4815
- error: noTxHashError
4816
- });
4817
- return;
4818
- }
4819
-
4820
- // If another tx with the same nonce is mined, set as failed.
4821
- if (this._checkIfNonceIsTaken(txMeta)) {
4822
- this.emit(TX_EVENTS.TX_DROPPED, {
4823
- txId
4824
- });
4825
- return;
4826
- }
4827
- try {
4828
- const transactionReceipt = await this.provider.request({
4829
- method: METHOD_TYPES.ETH_GET_TRANSACTION_RECEIPT,
4830
- params: [txHash]
4831
- });
4832
- if (transactionReceipt !== null && transactionReceipt !== void 0 && transactionReceipt.blockNumber) {
4833
- const {
4834
- baseFeePerGas,
4835
- timestamp
4836
- } = await this.provider.request({
4837
- method: METHOD_TYPES.ETH_GET_BLOCK_BY_HASH,
4838
- params: [transactionReceipt.blockHash, false]
4839
- });
4840
- this.emit(TX_EVENTS.TX_CONFIRMED, {
4841
- txId,
4842
- txReceipt: transactionReceipt,
4843
- baseFeePerGas,
4844
- blockTimestamp: timestamp
4845
- });
4846
- return;
4847
- }
4848
- } catch (error) {
4849
- log.error("error while loading tx", error);
4850
- txMeta.warning = {
4851
- error: error.message,
4852
- message: "There was a problem loading this transaction."
4853
- };
4854
- this.emit(TX_EVENTS.TX_WARNING, {
4855
- txMeta
4856
- });
4857
- }
4858
- if (await this._checkIfTxWasDropped(txMeta)) {
4859
- this.emit(TX_EVENTS.TX_DROPPED, {
4860
- txId
4861
- });
5011
+ this.lockMap[lockId] = mutex;
4862
5012
  }
5013
+ return mutex;
4863
5014
  }
4864
- async _checkIfTxWasDropped(txMeta) {
4865
- const {
4866
- transactionHash: txHash,
4867
- transaction: {
4868
- nonce,
4869
- from
4870
- }
4871
- } = txMeta;
4872
- const networkNextNonce = await this.provider.request({
5015
+ async _getNetworkNextNonce(address) {
5016
+ // calculate next nonce
5017
+ // we need to make sure our base count
5018
+ // and pending count are from the same block
5019
+ const block = await this.blockTracker.getLatestBlock();
5020
+ const baseCountStr = await this.provider.request({
4873
5021
  method: METHOD_TYPES.ETH_GET_TRANSACTION_COUNT,
4874
- params: [from, "latest"]
5022
+ params: [address, block.idempotencyKey]
4875
5023
  });
4876
- if (Number.parseInt(nonce, 16) >= Number.parseInt(networkNextNonce, 16)) {
4877
- return false;
4878
- }
4879
- if (!this.droppedBlocksBufferByHash.has(txHash)) {
4880
- this.droppedBlocksBufferByHash.set(txHash, 0);
4881
- }
4882
- const currentBlockBuffer = this.droppedBlocksBufferByHash.get(txHash);
4883
- if (currentBlockBuffer < this.DROPPED_BUFFER_COUNT) {
4884
- this.droppedBlocksBufferByHash.set(txHash, currentBlockBuffer + 1);
4885
- return false;
4886
- }
4887
- this.droppedBlocksBufferByHash.delete(txHash);
4888
- return true;
5024
+ const baseCount = Number.parseInt(baseCountStr, 16);
5025
+ const nonceDetails = {
5026
+ block,
5027
+ baseCount
5028
+ };
5029
+ return {
5030
+ name: "network",
5031
+ nonce: baseCount,
5032
+ details: nonceDetails
5033
+ };
4889
5034
  }
4890
- _checkIfNonceIsTaken(txMeta) {
4891
- const address = txMeta.transaction.from;
4892
- const completed = this.getConfirmedTransactions(address);
4893
- return completed.some(otherMeta => {
4894
- if (otherMeta.id === txMeta.id) {
4895
- return false;
4896
- }
4897
- return otherMeta.transaction.nonce === txMeta.transaction.nonce;
4898
- });
5035
+ _getHighestLocallyConfirmed(address) {
5036
+ const confirmedTransactions = this.getConfirmedTransactions(address);
5037
+ const highest = this._getHighestNonce(confirmedTransactions);
5038
+ return Number.isInteger(highest) ? highest + 1 : 0;
4899
5039
  }
4900
- }
4901
-
4902
- class TransactionGasUtil {
4903
- constructor(provider, blockTracker) {
4904
- _defineProperty(this, "provider", void 0);
4905
- _defineProperty(this, "blockTracker", void 0);
4906
- this.provider = provider;
4907
- this.blockTracker = blockTracker;
5040
+ _getHighestNonce(txList) {
5041
+ const nonces = txList.map(txMeta => {
5042
+ const {
5043
+ nonce
5044
+ } = txMeta.transaction;
5045
+ return Number.parseInt(nonce, 16);
5046
+ });
5047
+ const highestNonce = Math.max.apply(null, nonces);
5048
+ return highestNonce;
4908
5049
  }
4909
- async analyzeGasUsage(txMeta) {
4910
- const block = await this.blockTracker.getLatestBlock();
4911
- // fallback to block gasLimit
4912
- const blockGasLimitBN = new BN$1(stripHexPrefix(block.gasLimit), 16);
4913
- const saferGasLimitBN = blockGasLimitBN.mul(new BN$1(19)).div(new BN$1(20));
4914
- let estimatedGasHex = addHexPrefix(saferGasLimitBN.toString("hex"));
4915
- let simulationFails;
4916
- try {
4917
- estimatedGasHex = await this.estimateTxGas(txMeta);
4918
- } catch (error) {
4919
- log.warn(error);
4920
- simulationFails = {
4921
- reason: error.message,
4922
- errorKey: error.errorKey,
4923
- debug: {
4924
- blockNumber: block.idempotencyKey,
4925
- blockGasLimit: block.gasLimit
4926
- }
4927
- };
5050
+ _getHighestContinuousFrom(txList, startPoint) {
5051
+ const nonces = new Set(txList.map(txMeta => {
5052
+ const {
5053
+ nonce
5054
+ } = txMeta.transaction;
5055
+ return Number.parseInt(nonce, 16);
5056
+ }));
5057
+ let highest = startPoint;
5058
+ while (nonces.has(highest)) {
5059
+ highest += 1;
4928
5060
  }
4929
5061
  return {
4930
- blockGasLimit: block.gasLimit,
4931
- estimatedGasHex,
4932
- simulationFails
5062
+ name: "local",
5063
+ nonce: highest,
5064
+ details: {
5065
+ startPoint,
5066
+ highest
5067
+ }
4933
5068
  };
4934
5069
  }
5070
+ }
4935
5071
 
4936
- /**
4937
- Adds a gas buffer with out exceeding the block gas limit
4938
- */
4939
- addGasBuffer(initialGasLimitHex, blockGasLimitHex) {
4940
- let multiplier = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1.5;
4941
- const initialGasLimitBn = new BN$1(stripHexPrefix(initialGasLimitHex), 16);
4942
- const blockGasLimitBn = new BN$1(stripHexPrefix(blockGasLimitHex), 16);
4943
- const upperGasLimitBn = blockGasLimitBn.muln(0.9);
4944
- const bufferedGasLimitBn = initialGasLimitBn.muln(multiplier);
4945
-
4946
- // if initialGasLimit is above blockGasLimit, dont modify it
4947
- if (initialGasLimitBn.gt(upperGasLimitBn)) return addHexPrefix(initialGasLimitBn.toString("hex"));
4948
- // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit
4949
- if (bufferedGasLimitBn.lt(upperGasLimitBn)) return addHexPrefix(bufferedGasLimitBn.toString("hex"));
4950
- // otherwise use blockGasLimit
4951
- return addHexPrefix(upperGasLimitBn.toString("hex"));
5072
+ class PendingTransactionTracker extends SafeEventEmitter {
5073
+ constructor(_ref) {
5074
+ let {
5075
+ provider,
5076
+ nonceTracker,
5077
+ approveTransaction,
5078
+ publishTransaction,
5079
+ getPendingTransactions,
5080
+ getConfirmedTransactions
5081
+ } = _ref;
5082
+ super();
5083
+ _defineProperty(this, "DROPPED_BUFFER_COUNT", 3);
5084
+ _defineProperty(this, "nonceTracker", void 0);
5085
+ _defineProperty(this, "provider", void 0);
5086
+ _defineProperty(this, "approveTransaction", void 0);
5087
+ _defineProperty(this, "droppedBlocksBufferByHash", void 0);
5088
+ _defineProperty(this, "getConfirmedTransactions", void 0);
5089
+ _defineProperty(this, "getPendingTransactions", void 0);
5090
+ _defineProperty(this, "publishTransaction", void 0);
5091
+ this.provider = provider;
5092
+ this.nonceTracker = nonceTracker;
5093
+ this.approveTransaction = approveTransaction;
5094
+ this.publishTransaction = publishTransaction;
5095
+ this.getPendingTransactions = getPendingTransactions;
5096
+ this.getConfirmedTransactions = getConfirmedTransactions;
5097
+ this.droppedBlocksBufferByHash = new Map();
4952
5098
  }
4953
5099
 
4954
5100
  /**
4955
- Estimates the tx's gas usage
5101
+ checks the network for signed txs and releases the nonce global lock if it is
4956
5102
  */
4957
- async estimateTxGas(txMeta) {
4958
- const txParams = cloneDeep(txMeta.transaction);
4959
-
4960
- // `eth_estimateGas` can fail if the user has insufficient balance for the
4961
- // value being sent, or for the gas cost. We don't want to check their
4962
- // balance here, we just want the gas estimate. The gas price is removed
4963
- // to skip those balance checks. We check balance elsewhere. We also delete
4964
- // maxFeePerGas and maxPriorityFeePerGas to support EIP-1559 txs.
4965
- delete txParams.gasPrice;
4966
- delete txParams.maxFeePerGas;
4967
- delete txParams.maxPriorityFeePerGas;
4968
- return this.provider.request({
4969
- method: "eth_estimateGas",
4970
- params: [txParams]
4971
- });
4972
- }
4973
- }
4974
-
4975
- /**
4976
- Generates an array of history objects sense the previous state.
4977
- The object has the keys
4978
- op (the operation performed),
4979
- path (the key and if a nested object then each key will be seperated with a `/`)
4980
- value
4981
- with the first entry having the note and a timestamp when the change took place
4982
- */
4983
- function generateHistoryEntry(previousState, newState, note) {
4984
- const entry = jsonDiffer.compare(previousState, newState);
4985
- // Add a note to the first op, since it breaks if we append it to the entry
4986
- if (entry[0]) {
4987
- if (note) {
4988
- entry[0].note = note;
5103
+ async updatePendingTxs() {
5104
+ // in order to keep the nonceTracker accurate we block it while updating pending transactions
5105
+ const nonceGlobalLock = await this.nonceTracker.getGlobalLock();
5106
+ try {
5107
+ const pendingTxs = this.getPendingTransactions();
5108
+ await Promise.all(pendingTxs.map(txMeta => this._checkPendingTx(txMeta)));
5109
+ } catch (error) {
5110
+ log.error("PendingTransactionTracker - Error updating pending transactions");
5111
+ log.error(error);
4989
5112
  }
4990
- entry[0].timestamp = Date.now();
4991
- }
4992
- return entry;
4993
- }
4994
-
4995
- /**
4996
- Recovers previous txMeta state obj
4997
- */
4998
- function replayHistory(_shortHistory) {
4999
- const shortHistory = cloneDeep(_shortHistory);
5000
- return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument);
5001
- }
5002
- function snapshotFromTxMeta(txMeta) {
5003
- const shallow = _objectSpread({}, txMeta);
5004
- delete shallow.history;
5005
- return cloneDeep(shallow);
5006
- }
5007
-
5008
- const erc20Interface = new Interface(erc20Abi);
5009
- const erc721Interface = new Interface(erc721Abi);
5010
- const erc1155Interface = new Interface(erc1155Abi);
5011
-
5012
- // functions that handle normalizing of that key in txParams
5013
-
5014
- const normalizers = {
5015
- from: function (from) {
5016
- let LowerCase = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
5017
- return LowerCase ? addHexPrefix(from).toLowerCase() : addHexPrefix(from);
5018
- },
5019
- to: function (to) {
5020
- let LowerCase = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
5021
- return LowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to);
5022
- },
5023
- nonce: nonce => addHexPrefix(nonce),
5024
- customNonceValue: nonce => addHexPrefix(nonce),
5025
- value: value => addHexPrefix(value),
5026
- data: data => addHexPrefix(data),
5027
- gas: gas => addHexPrefix(gas),
5028
- gasPrice: gasPrice => addHexPrefix(gasPrice),
5029
- type: addHexPrefix,
5030
- maxFeePerGas: addHexPrefix,
5031
- maxPriorityFeePerGas: addHexPrefix
5032
- };
5033
-
5034
- /**
5035
- * normalizes txParams
5036
- */
5037
- function normalizeTxParameters(txParameters) {
5038
- let lowerCase = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
5039
- // apply only keys in the normalizers
5040
- const normalizedTxParameters = {
5041
- id: txParameters.id || randomId(),
5042
- from: txParameters.from
5043
- };
5044
- for (const key in normalizers) {
5045
- const currentKey = key;
5046
- if (txParameters[currentKey])
5047
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5048
- normalizedTxParameters[currentKey] = normalizers[currentKey](txParameters[currentKey], lowerCase);
5113
+ nonceGlobalLock.releaseLock();
5049
5114
  }
5050
- return normalizedTxParameters;
5051
- }
5052
- function transactionMatchesNetwork(transaction, chainId) {
5053
- if (typeof transaction.chainId !== "undefined") {
5054
- return transaction.chainId === chainId;
5115
+ async resubmitPendingTxs(block) {
5116
+ const pending = this.getPendingTransactions();
5117
+ // only try resubmitting if their are transactions to resubmit
5118
+ if (pending.length === 0) return;
5119
+ // Keep this as a for loop because we want to wait for each item to be submitted
5120
+ for (const txMeta of pending) {
5121
+ try {
5122
+ await this._resubmitTx(txMeta, block.idempotencyKey);
5123
+ } catch (error) {
5124
+ var _value;
5125
+ /*
5126
+ Dont marked as failed if the error is a "known" transaction warning
5127
+ "there is already a transaction with the same sender-nonce
5128
+ but higher/same gas price"
5129
+ Also don't mark as failed if it has ever been broadcast successfully.
5130
+ A successful broadcast means it may still be mined.
5131
+ */
5132
+ const errorMessage = ((_value = error.value) === null || _value === void 0 || (_value = _value.message) === null || _value === void 0 ? void 0 : _value.toLowerCase()) || error.message.toLowerCase();
5133
+ const isKnownTx =
5134
+ // geth
5135
+ errorMessage.includes("replacement transaction underpriced") || errorMessage.includes("known transaction") ||
5136
+ // parity
5137
+ errorMessage.includes("gas price too low to replace") || errorMessage.includes("transaction with the same hash was already imported") ||
5138
+ // other
5139
+ errorMessage.includes("gateway timeout") || errorMessage.includes("nonce too low");
5140
+ // ignore resubmit warnings, return early
5141
+ if (isKnownTx) return;
5142
+ // encountered real error - transition to error state
5143
+ txMeta.warning = {
5144
+ error: errorMessage,
5145
+ message: "There was an error when resubmitting this transaction."
5146
+ };
5147
+ this.emit(TX_EVENTS.TX_WARNING, {
5148
+ txMeta,
5149
+ error,
5150
+ txId: txMeta.id
5151
+ });
5152
+ }
5153
+ }
5055
5154
  }
5056
- return false;
5057
- }
5058
-
5059
- /**
5060
- * Determines if the maxFeePerGas and maxPriorityFeePerGas fields are supplied
5061
- * and valid inputs. This will return false for non hex string inputs.
5062
- * the transaction to check
5063
- * @returns true if transaction uses valid EIP1559 fields
5064
- */
5065
- function isEIP1559Transaction(transaction) {
5066
- var _transaction$transact, _transaction$transact2;
5067
- return isHexString(addHexPrefix(transaction === null || transaction === void 0 || (_transaction$transact = transaction.transaction) === null || _transaction$transact === void 0 ? void 0 : _transaction$transact.maxFeePerGas)) && isHexString(addHexPrefix(transaction === null || transaction === void 0 || (_transaction$transact2 = transaction.transaction) === null || _transaction$transact2 === void 0 ? void 0 : _transaction$transact2.maxPriorityFeePerGas));
5068
- }
5155
+ async _resubmitTx(txMeta, latestBlockNumber) {
5156
+ if (!txMeta.firstRetryBlockNumber) {
5157
+ this.emit(TX_EVENTS.TX_BLOCK_UPDATE, {
5158
+ txMeta,
5159
+ latestBlockNumber,
5160
+ txId: txMeta.id
5161
+ });
5162
+ }
5163
+ const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber;
5164
+ const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16);
5165
+ const retryCount = txMeta.retryCount || 0;
5069
5166
 
5070
- /**
5071
- * Determine if the maxFeePerGas and maxPriorityFeePerGas fields are not
5072
- * supplied and that the gasPrice field is valid if it is provided. This will
5073
- * return false if gasPrice is a non hex string.
5074
- * transaction -
5075
- * the transaction to check
5076
- * @returns true if transaction uses valid Legacy fields OR lacks
5077
- * EIP1559 fields
5078
- */
5079
- function isLegacyTransaction(transaction) {
5080
- return typeof transaction.transaction.maxFeePerGas === "undefined" && typeof transaction.transaction.maxPriorityFeePerGas === "undefined" && (typeof transaction.transaction.gasPrice === "undefined" || isHexString(addHexPrefix(transaction.transaction.gasPrice)));
5081
- }
5167
+ // Exponential backoff to limit retries at publishing (capped at last 15 mins)
5168
+ if (txBlockDistance <= Math.min(50, 2 ** retryCount)) return undefined;
5082
5169
 
5083
- /**
5084
- * Given two fields, ensure that the second field is not included in txParams,
5085
- * and if it is throw an invalidParams error.
5086
- */
5087
- function ensureMutuallyExclusiveFieldsNotProvided(txParams, fieldBeingValidated, mutuallyExclusiveField) {
5088
- if (typeof txParams[mutuallyExclusiveField] !== "undefined") {
5089
- throw rpcErrors.invalidParams(`Invalid transaction params: specified ${fieldBeingValidated} but also included ${mutuallyExclusiveField}, these cannot be mixed`);
5090
- }
5091
- }
5170
+ // Only auto-submit already-signed txs:
5171
+ if (!("rawTx" in txMeta)) return this.approveTransaction(txMeta.id);
5172
+ const {
5173
+ rawTx
5174
+ } = txMeta;
5175
+ const txHash = await this.publishTransaction(rawTx);
5092
5176
 
5093
- /**
5094
- * Ensures that the provided value for field is a string, throws an
5095
- * invalidParams error if field is not a string.
5096
- */
5097
- function ensureFieldIsString(txParams, field) {
5098
- if (typeof txParams[field] !== "string") {
5099
- throw rpcErrors.invalidParams(`Invalid transaction params: ${field} is not a string. got: (${txParams[field]})`);
5177
+ // Increment successful tries:
5178
+ this.emit(TX_EVENTS.TX_RETRY, {
5179
+ txMeta,
5180
+ txId: txMeta.id
5181
+ });
5182
+ return txHash;
5100
5183
  }
5101
- }
5184
+ async _checkPendingTx(foundTx) {
5185
+ const txMeta = foundTx;
5186
+ const txHash = txMeta.transactionHash;
5187
+ const txId = txMeta.id;
5102
5188
 
5103
- /**
5104
- * Ensures that the provided txParams has the proper 'type' specified for the
5105
- * given field, if it is provided. If types do not match throws an
5106
- * invalidParams error.
5107
- */
5108
- function ensureProperTransactionEnvelopeTypeProvided(txParams, field) {
5109
- switch (field) {
5110
- case "maxFeePerGas":
5111
- case "maxPriorityFeePerGas":
5112
- if (txParams.type && txParams.type !== TRANSACTION_ENVELOPE_TYPES.FEE_MARKET) {
5113
- throw rpcErrors.invalidParams(`Invalid transaction envelope type: specified type "${txParams.type}" but ` + `including maxFeePerGas and maxPriorityFeePerGas requires type: "${TRANSACTION_ENVELOPE_TYPES.FEE_MARKET}"`);
5114
- }
5115
- break;
5116
- case "gasPrice":
5117
- default:
5118
- if (txParams.type && txParams.type === TRANSACTION_ENVELOPE_TYPES.FEE_MARKET) {
5119
- throw rpcErrors.invalidParams(`Invalid transaction envelope type: specified type "${txParams.type}" but ` + "included a gasPrice instead of maxFeePerGas and maxPriorityFeePerGas");
5120
- }
5121
- }
5122
- }
5189
+ // Only check submitted txs
5190
+ if (txMeta.status !== TransactionStatus.submitted) return;
5123
5191
 
5124
- /**
5125
- * validates the from field in txParams
5126
- */
5127
- function validateFrom(txParams) {
5128
- if (!(typeof txParams.from === "string")) {
5129
- throw rpcErrors.invalidParams(`Invalid "from" address "${txParams.from}": not a string.`);
5130
- }
5131
- if (!isValidAddress(txParams.from)) {
5132
- throw rpcErrors.invalidParams('Invalid "from" address.');
5133
- }
5134
- }
5192
+ // extra check in case there was an uncaught error during the
5193
+ // signature and submission process
5194
+ if (!txHash) {
5195
+ const noTxHashError = new Error("We had an error while submitting this transaction, please try again.");
5196
+ noTxHashError.name = "NoTxHashError";
5197
+ this.emit(TX_EVENTS.TX_FAILED, {
5198
+ txId,
5199
+ error: noTxHashError
5200
+ });
5201
+ return;
5202
+ }
5135
5203
 
5136
- /**
5137
- * validates the to field in txParams
5138
- */
5139
- function validateRecipient(txParameters) {
5140
- if (txParameters.to === "0x" || txParameters.to === null) {
5141
- if (txParameters.data) {
5142
- delete txParameters.to;
5143
- } else {
5144
- throw rpcErrors.invalidParams('Invalid "to" address.');
5204
+ // If another tx with the same nonce is mined, set as failed.
5205
+ if (this._checkIfNonceIsTaken(txMeta)) {
5206
+ this.emit(TX_EVENTS.TX_DROPPED, {
5207
+ txId
5208
+ });
5209
+ return;
5210
+ }
5211
+ try {
5212
+ const transactionReceipt = await this.provider.request({
5213
+ method: METHOD_TYPES.ETH_GET_TRANSACTION_RECEIPT,
5214
+ params: [txHash]
5215
+ });
5216
+ if (transactionReceipt !== null && transactionReceipt !== void 0 && transactionReceipt.blockNumber) {
5217
+ const {
5218
+ baseFeePerGas,
5219
+ timestamp
5220
+ } = await this.provider.request({
5221
+ method: METHOD_TYPES.ETH_GET_BLOCK_BY_HASH,
5222
+ params: [transactionReceipt.blockHash, false]
5223
+ });
5224
+ this.emit(TX_EVENTS.TX_CONFIRMED, {
5225
+ txId,
5226
+ txReceipt: transactionReceipt,
5227
+ baseFeePerGas,
5228
+ blockTimestamp: timestamp
5229
+ });
5230
+ return;
5231
+ }
5232
+ } catch (error) {
5233
+ log.error("error while loading tx", error);
5234
+ txMeta.warning = {
5235
+ error: error.message,
5236
+ message: "There was a problem loading this transaction."
5237
+ };
5238
+ this.emit(TX_EVENTS.TX_WARNING, {
5239
+ txMeta
5240
+ });
5241
+ }
5242
+ if (await this._checkIfTxWasDropped(txMeta)) {
5243
+ this.emit(TX_EVENTS.TX_DROPPED, {
5244
+ txId
5245
+ });
5145
5246
  }
5146
- } else if (txParameters.to !== undefined && !isValidAddress(txParameters.to)) {
5147
- throw rpcErrors.invalidParams('Invalid "to" address.');
5148
5247
  }
5149
- return txParameters;
5150
- }
5151
-
5152
- /**
5153
- * Validates the given tx parameters
5154
- * @throws if the tx params contains invalid fields
5155
- */
5156
- function validateTxParameters(txParams) {
5157
- let eip1559Compatibility = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
5158
- if (!txParams || typeof txParams !== "object" || Array.isArray(txParams)) {
5159
- throw rpcErrors.invalidParams("Invalid transaction params: must be an object.");
5248
+ async _checkIfTxWasDropped(txMeta) {
5249
+ const {
5250
+ transactionHash: txHash,
5251
+ transaction: {
5252
+ nonce,
5253
+ from
5254
+ }
5255
+ } = txMeta;
5256
+ const networkNextNonce = await this.provider.request({
5257
+ method: METHOD_TYPES.ETH_GET_TRANSACTION_COUNT,
5258
+ params: [from, "latest"]
5259
+ });
5260
+ if (Number.parseInt(nonce, 16) >= Number.parseInt(networkNextNonce, 16)) {
5261
+ return false;
5262
+ }
5263
+ if (!this.droppedBlocksBufferByHash.has(txHash)) {
5264
+ this.droppedBlocksBufferByHash.set(txHash, 0);
5265
+ }
5266
+ const currentBlockBuffer = this.droppedBlocksBufferByHash.get(txHash);
5267
+ if (currentBlockBuffer < this.DROPPED_BUFFER_COUNT) {
5268
+ this.droppedBlocksBufferByHash.set(txHash, currentBlockBuffer + 1);
5269
+ return false;
5270
+ }
5271
+ this.droppedBlocksBufferByHash.delete(txHash);
5272
+ return true;
5160
5273
  }
5161
- if (!txParams.to && !txParams.data) {
5162
- throw rpcErrors.invalidParams('Invalid transaction params: must specify "data" for contract deployments, or "to" (and optionally "data") for all other types of transactions.');
5274
+ _checkIfNonceIsTaken(txMeta) {
5275
+ const address = txMeta.transaction.from;
5276
+ const completed = this.getConfirmedTransactions(address);
5277
+ return completed.some(otherMeta => {
5278
+ if (otherMeta.id === txMeta.id) {
5279
+ return false;
5280
+ }
5281
+ return otherMeta.transaction.nonce === txMeta.transaction.nonce;
5282
+ });
5163
5283
  }
5164
- if (isEIP1559Transaction({
5165
- transaction: txParams
5166
- }) && !eip1559Compatibility) {
5167
- throw rpcErrors.invalidParams("Invalid transaction params: params specify an EIP-1559 transaction but the current network does not support EIP-1559");
5284
+ }
5285
+
5286
+ class TransactionGasUtil {
5287
+ constructor(provider, blockTracker) {
5288
+ _defineProperty(this, "provider", void 0);
5289
+ _defineProperty(this, "blockTracker", void 0);
5290
+ this.provider = provider;
5291
+ this.blockTracker = blockTracker;
5168
5292
  }
5169
- Object.entries(txParams).forEach(_ref => {
5170
- let [key, value] = _ref;
5171
- // validate types
5172
- switch (key) {
5173
- case "from":
5174
- validateFrom(txParams);
5175
- break;
5176
- case "to":
5177
- validateRecipient(txParams);
5178
- break;
5179
- case "gasPrice":
5180
- ensureProperTransactionEnvelopeTypeProvided(txParams, "gasPrice");
5181
- ensureMutuallyExclusiveFieldsNotProvided(txParams, "gasPrice", "maxFeePerGas");
5182
- ensureMutuallyExclusiveFieldsNotProvided(txParams, "gasPrice", "maxPriorityFeePerGas");
5183
- ensureFieldIsString(txParams, "gasPrice");
5184
- break;
5185
- case "maxFeePerGas":
5186
- ensureProperTransactionEnvelopeTypeProvided(txParams, "maxFeePerGas");
5187
- ensureMutuallyExclusiveFieldsNotProvided(txParams, "maxFeePerGas", "gasPrice");
5188
- ensureFieldIsString(txParams, "maxFeePerGas");
5189
- break;
5190
- case "maxPriorityFeePerGas":
5191
- ensureProperTransactionEnvelopeTypeProvided(txParams, "maxPriorityFeePerGas");
5192
- ensureMutuallyExclusiveFieldsNotProvided(txParams, "maxPriorityFeePerGas", "gasPrice");
5193
- ensureFieldIsString(txParams, "maxPriorityFeePerGas");
5194
- break;
5195
- case "value":
5196
- ensureFieldIsString(txParams, "value");
5197
- if (value.toString().includes("-")) {
5198
- throw rpcErrors.invalidParams(`Invalid transaction value "${value}": not a positive number.`);
5199
- }
5200
- if (value.toString().includes(".")) {
5201
- throw rpcErrors.invalidParams(`Invalid transaction value of "${value}": number must be in wei.`);
5202
- }
5203
- break;
5204
- case "chainId":
5205
- if (typeof value !== "number" && typeof value !== "string") {
5206
- throw rpcErrors.invalidParams(`Invalid transaction params: ${key} is not a Number or hex string. got: (${value})`);
5293
+ async analyzeGasUsage(txMeta) {
5294
+ const block = await this.blockTracker.getLatestBlock();
5295
+ // fallback to block gasLimit
5296
+ const blockGasLimitBN = new BN$1(stripHexPrefix(block.gasLimit), 16);
5297
+ const saferGasLimitBN = blockGasLimitBN.mul(new BN$1(19)).div(new BN$1(20));
5298
+ let estimatedGasHex = addHexPrefix(saferGasLimitBN.toString("hex"));
5299
+ let simulationFails;
5300
+ try {
5301
+ estimatedGasHex = await this.estimateTxGas(txMeta);
5302
+ } catch (error) {
5303
+ log.warn(error);
5304
+ simulationFails = {
5305
+ reason: error.message,
5306
+ errorKey: error.errorKey,
5307
+ debug: {
5308
+ blockNumber: block.idempotencyKey,
5309
+ blockGasLimit: block.gasLimit
5207
5310
  }
5208
- break;
5209
- default:
5210
- ensureFieldIsString(txParams, key);
5311
+ };
5211
5312
  }
5212
- });
5213
- }
5214
- function normalizeAndValidateTxParams(txParams) {
5215
- let lowerCase = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
5216
- const normalizedTxParams = normalizeTxParameters(txParams, lowerCase);
5217
- validateTxParameters(normalizedTxParams);
5218
- return normalizedTxParams;
5219
- }
5220
-
5221
- /**
5222
- * @returns an array of states that can be considered final
5223
- */
5224
- function getFinalStates() {
5225
- return [TransactionStatus.rejected,
5226
- // the user has responded no!
5227
- TransactionStatus.confirmed,
5228
- // the tx has been included in a block.
5229
- TransactionStatus.failed,
5230
- // the tx failed for some reason, included on tx data.
5231
- TransactionStatus.dropped // the tx nonce was already used
5232
- ];
5233
- }
5234
- function parseStandardTokenTransactionData(data) {
5235
- try {
5236
- const txDesc = erc20Interface.parseTransaction({
5237
- data
5238
- });
5239
- if (txDesc) return {
5240
- name: txDesc.name,
5241
- methodParams: txDesc.args.toArray(),
5242
- type: CONTRACT_TYPE_ERC20
5313
+ return {
5314
+ blockGasLimit: block.gasLimit,
5315
+ estimatedGasHex,
5316
+ simulationFails
5243
5317
  };
5244
- } catch {
5245
- // ignore and next try to parse with erc721 ABI
5246
5318
  }
5247
- try {
5248
- const txDesc = erc721Interface.parseTransaction({
5249
- data
5250
- });
5251
- if (txDesc) return {
5252
- name: txDesc.name,
5253
- methodParams: txDesc.args.toArray(),
5254
- type: CONTRACT_TYPE_ERC721
5255
- };
5256
- } catch {
5257
- // ignore and next try to parse with erc1155 ABI
5319
+
5320
+ /**
5321
+ Adds a gas buffer with out exceeding the block gas limit
5322
+ */
5323
+ addGasBuffer(initialGasLimitHex, blockGasLimitHex) {
5324
+ let multiplier = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1.5;
5325
+ const initialGasLimitBn = new BN$1(stripHexPrefix(initialGasLimitHex), 16);
5326
+ const blockGasLimitBn = new BN$1(stripHexPrefix(blockGasLimitHex), 16);
5327
+ const upperGasLimitBn = blockGasLimitBn.muln(0.9);
5328
+ const bufferedGasLimitBn = initialGasLimitBn.muln(multiplier);
5329
+
5330
+ // if initialGasLimit is above blockGasLimit, dont modify it
5331
+ if (initialGasLimitBn.gt(upperGasLimitBn)) return addHexPrefix(initialGasLimitBn.toString("hex"));
5332
+ // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit
5333
+ if (bufferedGasLimitBn.lt(upperGasLimitBn)) return addHexPrefix(bufferedGasLimitBn.toString("hex"));
5334
+ // otherwise use blockGasLimit
5335
+ return addHexPrefix(upperGasLimitBn.toString("hex"));
5258
5336
  }
5259
- try {
5260
- const txDesc = erc1155Interface.parseTransaction({
5261
- data
5337
+
5338
+ /**
5339
+ Estimates the tx's gas usage
5340
+ */
5341
+ async estimateTxGas(txMeta) {
5342
+ const txParams = cloneDeep(txMeta.transaction);
5343
+
5344
+ // `eth_estimateGas` can fail if the user has insufficient balance for the
5345
+ // value being sent, or for the gas cost. We don't want to check their
5346
+ // balance here, we just want the gas estimate. The gas price is removed
5347
+ // to skip those balance checks. We check balance elsewhere. We also delete
5348
+ // maxFeePerGas and maxPriorityFeePerGas to support EIP-1559 txs.
5349
+ delete txParams.gasPrice;
5350
+ delete txParams.maxFeePerGas;
5351
+ delete txParams.maxPriorityFeePerGas;
5352
+ return this.provider.request({
5353
+ method: "eth_estimateGas",
5354
+ params: [txParams]
5262
5355
  });
5263
- if (txDesc) return {
5264
- name: txDesc.name,
5265
- methodParams: txDesc.args.toArray(),
5266
- type: CONTRACT_TYPE_ERC1155
5267
- };
5268
- } catch {
5269
- // ignore and return undefined
5270
5356
  }
5271
- return undefined;
5272
5357
  }
5273
- const readAddressAsContract = async (provider, address) => {
5274
- let contractCode;
5275
- try {
5276
- contractCode = await provider.request({
5277
- method: METHOD_TYPES.ETH_GET_CODE,
5278
- params: [address, "latest"]
5279
- });
5280
- } catch (e) {
5281
- contractCode = null;
5282
- }
5283
- const isContractAddress = contractCode ? contractCode !== "0x" && contractCode !== "0x0" : false;
5284
- return {
5285
- contractCode,
5286
- isContractAddress
5287
- };
5288
- };
5289
- async function determineTransactionType(txParams, provider) {
5290
- const {
5291
- data,
5292
- to
5293
- } = txParams;
5294
- let name = "";
5295
- let methodParams = [];
5296
- let type = "";
5297
- try {
5298
- ({
5299
- name,
5300
- methodParams,
5301
- type
5302
- } = data && parseStandardTokenTransactionData(data) || {});
5303
- } catch (error) {
5304
- log.debug("Failed to parse transaction data", error);
5305
- }
5306
- let result;
5307
- let contractCode = "";
5308
- if (data && !to) {
5309
- result = TRANSACTION_TYPES.DEPLOY_CONTRACT;
5310
- } else {
5311
- const {
5312
- contractCode: resultCode,
5313
- isContractAddress
5314
- } = await readAddressAsContract(provider, to);
5315
- contractCode = resultCode;
5316
- if (isContractAddress) {
5317
- const valueExists = txParams.value && Number(txParams.value) !== 0;
5318
- const tokenMethodName = [TRANSACTION_TYPES.TOKEN_METHOD_APPROVE, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM, TRANSACTION_TYPES.COLLECTIBLE_METHOD_SAFE_TRANSFER_FROM, TRANSACTION_TYPES.SET_APPROVAL_FOR_ALL].find(x => {
5319
- var _name;
5320
- return x.toLowerCase() === ((_name = name) === null || _name === void 0 ? void 0 : _name.toLowerCase());
5321
- });
5322
- result = data && tokenMethodName && !valueExists ? tokenMethodName : TRANSACTION_TYPES.CONTRACT_INTERACTION;
5323
- } else {
5324
- result = TRANSACTION_TYPES.SENT_ETHER;
5358
+
5359
+ /**
5360
+ Generates an array of history objects sense the previous state.
5361
+ The object has the keys
5362
+ op (the operation performed),
5363
+ path (the key and if a nested object then each key will be seperated with a `/`)
5364
+ value
5365
+ with the first entry having the note and a timestamp when the change took place
5366
+ */
5367
+ function generateHistoryEntry(previousState, newState, note) {
5368
+ const entry = jsonDiffer.compare(previousState, newState);
5369
+ // Add a note to the first op, since it breaks if we append it to the entry
5370
+ if (entry[0]) {
5371
+ if (note) {
5372
+ entry[0].note = note;
5325
5373
  }
5374
+ entry[0].timestamp = Date.now();
5326
5375
  }
5327
- return {
5328
- type: type || CONTRACT_TYPE_ETH,
5329
- category: result,
5330
- methodParams,
5331
- getCodeResponse: contractCode
5332
- };
5376
+ return entry;
5377
+ }
5378
+
5379
+ /**
5380
+ Recovers previous txMeta state obj
5381
+ */
5382
+ function replayHistory(_shortHistory) {
5383
+ const shortHistory = cloneDeep(_shortHistory);
5384
+ return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument);
5385
+ }
5386
+ function snapshotFromTxMeta(txMeta) {
5387
+ const shallow = _objectSpread({}, txMeta);
5388
+ delete shallow.history;
5389
+ return cloneDeep(shallow);
5333
5390
  }
5334
5391
 
5335
5392
  class TransactionStateManager extends BaseTransactionStateManager {
@@ -6110,5 +6167,5 @@ class TransactionController extends TransactionStateManager {
6110
6167
  }
6111
6168
  }
6112
6169
 
6113
- export { ARBITRUM_MAINNET_CHAIN_ID, ARBITRUM_TESTNET_CHAIN_ID, AVALANCHE_MAINNET_CHAIN_ID, AVALANCHE_TESTNET_CHAIN_ID, AccountTrackerController, AddChainController, BASE_CHAIN_ID, BASE_TESTNET_CHAIN_ID, BSC_MAINNET_CHAIN_ID, BSC_TESTNET_CHAIN_ID, CELO_MAINNET_CHAIN_ID, CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP, COINGECKO_PLATFORMS_CHAIN_CODE_MAP, COINGECKO_SUPPORTED_CURRENCIES, CONTRACT_TYPE_ERC1155, CONTRACT_TYPE_ERC20, CONTRACT_TYPE_ERC721, CONTRACT_TYPE_ETH, CurrencyController, DEFAULT_CURRENCY, DecryptMessageController, ERC1155_INTERFACE_ID, ERC721_ENUMERABLE_INTERFACE_ID, ERC721_INTERFACE_ID, ERC721_METADATA_INTERFACE_ID, ETHERSCAN_SUPPORTED_CHAINS, EncryptionPublicKeyController, GAS_ESTIMATE_TYPES, GAS_LIMITS, GasFeeController, KeyringController, LOCALHOST, MAINNET_CHAIN_ID, MESSAGE_EVENTS, METHOD_TYPES, MessageController, MessageStatus, NetworkController, NftHandler, NftsController, NonceTracker, OLD_ERC721_LIST, OPTIMISM_MAINNET_CHAIN_ID, OPTIMISM_TESTNET_CHAIN_ID, POLYGON_CHAIN_ID, POLYGON_MUMBAI_CHAIN_ID, PendingTransactionTracker, PersonalMessageController, PollingBlockTracker, PreferencesController, SEPOLIA_CHAIN_ID, SIMPLEHASH_SUPPORTED_CHAINS, SUPPORTED_NETWORKS, SwitchChainController, TEST_CHAINS, TRANSACTION_ENVELOPE_TYPES, TokenHandler, TokenRatesController, TokensController, TransactionController, TransactionGasUtil, TransactionStateManager, TypedMessageController, XDAI_CHAIN_ID, addCurrencies, bnLessThan, conversionGTE, conversionGreaterThan, conversionLTE, conversionLessThan, conversionMax, conversionUtil, createChainIdMiddleware, createEthereumMiddleware, createGetAccountsMiddleware, createJsonRpcClient, createPendingNonceMiddleware, createPendingTxMiddleware, createProcessAddEthereumChain, createProcessDecryptMessageMiddleware, createProcessEncryptionPublicKeyMiddleware, createProcessEthSignMessage, createProcessPersonalMessage, createProcessSwitchEthereumChain, createProcessTransactionMiddleware, createProcessTypedMessage, createProcessTypedMessageV3, createProcessTypedMessageV4, createProviderConfigMiddleware, createRequestAccountsMiddleware, decGWEIToHexWEI, determineTransactionType, ensureFieldIsString, ensureMutuallyExclusiveFieldsNotProvided, erc1155Abi, erc20Abi, erc721Abi, formatDate, formatPastTx, formatTime, formatTxMetaForRpcResult, generateHistoryEntry, getBigNumber, getChainType, getEthTxStatus, getEtherScanHashLink, getFinalStates, getIpfsEndpoint, hexWEIToDecGWEI, idleTimeTracker, isAddressByChainId, isEIP1559Transaction, isLegacyTransaction, multiplyCurrencies, normalizeAndValidateTxParams, normalizeMessageData, normalizeTxParameters, parseDecryptMessageData, parseStandardTokenTransactionData, readAddressAsContract, replayHistory, sanitizeNftMetdataUrl, singleBalanceCheckerAbi, snapshotFromTxMeta, subtractCurrencies, toChecksumAddressByChainId, toNegative, transactionMatchesNetwork, validateAddChainData, validateAddress, validateDecryptedMessageData, validateEncryptionPublicKeyMessageData, validateFrom, validateRecipient, validateSignMessageData, validateSwitchChainData, validateTxParameters, validateTypedSignMessageDataV1, validateTypedSignMessageDataV3V4 };
6170
+ export { ARBITRUM_MAINNET_CHAIN_ID, ARBITRUM_TESTNET_CHAIN_ID, AVALANCHE_MAINNET_CHAIN_ID, AVALANCHE_TESTNET_CHAIN_ID, AccountTrackerController, AddChainController, BASE_CHAIN_ID, BASE_TESTNET_CHAIN_ID, BSC_MAINNET_CHAIN_ID, BSC_TESTNET_CHAIN_ID, CELO_MAINNET_CHAIN_ID, CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP, COINGECKO_PLATFORMS_CHAIN_CODE_MAP, COINGECKO_SUPPORTED_CURRENCIES, CONTRACT_TYPE_ERC1155, CONTRACT_TYPE_ERC20, CONTRACT_TYPE_ERC721, CONTRACT_TYPE_ETH, CurrencyController, DEFAULT_CURRENCY, DecryptMessageController, ERC1155_INTERFACE_ID, ERC721_ENUMERABLE_INTERFACE_ID, ERC721_INTERFACE_ID, ERC721_METADATA_INTERFACE_ID, ETHERSCAN_SUPPORTED_CHAINS, EncryptionPublicKeyController, GAS_ESTIMATE_TYPES, GAS_LIMITS, GasFeeController, KeyringController, LOCALHOST, MAINNET_CHAIN_ID, MESSAGE_EVENTS, METHOD_TYPES, MessageController, MessageStatus, NetworkController, NftHandler, NftsController, NonceTracker, OLD_ERC721_LIST, OPTIMISM_MAINNET_CHAIN_ID, OPTIMISM_TESTNET_CHAIN_ID, POLYGON_CHAIN_ID, POLYGON_MUMBAI_CHAIN_ID, PendingTransactionTracker, PersonalMessageController, PollingBlockTracker, PreferencesController, SEPOLIA_CHAIN_ID, SIMPLEHASH_SUPPORTED_CHAINS, SUPPORTED_NETWORKS, SwitchChainController, TEST_CHAINS, TRANSACTION_ENVELOPE_TYPES, TokenHandler, TokenRatesController, TokensController, TransactionController, TransactionGasUtil, TransactionStateManager, TypedMessageController, XDAI_CHAIN_ID, addCurrencies, addEtherscanTransactions, bnLessThan, conversionGTE, conversionGreaterThan, conversionLTE, conversionLessThan, conversionMax, conversionUtil, createChainIdMiddleware, createEthereumMiddleware, createGetAccountsMiddleware, createJsonRpcClient, createPendingNonceMiddleware, createPendingTxMiddleware, createProcessAddEthereumChain, createProcessDecryptMessageMiddleware, createProcessEncryptionPublicKeyMiddleware, createProcessEthSignMessage, createProcessPersonalMessage, createProcessSwitchEthereumChain, createProcessTransactionMiddleware, createProcessTypedMessage, createProcessTypedMessageV3, createProcessTypedMessageV4, createProviderConfigMiddleware, createRequestAccountsMiddleware, decGWEIToHexWEI, determineTransactionType, ensureFieldIsString, ensureMutuallyExclusiveFieldsNotProvided, erc1155Abi, erc20Abi, erc721Abi, formatDate, formatPastTx, formatTime, formatTxMetaForRpcResult, generateHistoryEntry, getBigNumber, getChainType, getEthTxStatus, getEtherScanHashLink, getFinalStates, getIpfsEndpoint, hexWEIToDecGWEI, idleTimeTracker, isAddressByChainId, isEIP1559Transaction, isLegacyTransaction, multiplyCurrencies, normalizeAndValidateTxParams, normalizeMessageData, normalizeTxParameters, parseDecryptMessageData, parseStandardTokenTransactionData, readAddressAsContract, replayHistory, sanitizeNftMetdataUrl, singleBalanceCheckerAbi, snapshotFromTxMeta, subtractCurrencies, toChecksumAddressByChainId, toNegative, transactionMatchesNetwork, validateAddChainData, validateAddress, validateDecryptedMessageData, validateEncryptionPublicKeyMessageData, validateFrom, validateRecipient, validateSignMessageData, validateSwitchChainData, validateTxParameters, validateTypedSignMessageDataV1, validateTypedSignMessageDataV3V4 };
6114
6171
  //# sourceMappingURL=ethereumControllers.esm.js.map