@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.
- package/dist/ethereumControllers.cjs.js +616 -555
- package/dist/ethereumControllers.cjs.js.map +1 -1
- package/dist/ethereumControllers.esm.js +1118 -1061
- package/dist/ethereumControllers.esm.js.map +1 -1
- package/dist/ethereumControllers.umd.min.js +1 -1
- package/dist/ethereumControllers.umd.min.js.map +1 -1
- package/dist/types/Preferences/PreferencesController.d.ts +2 -2
- package/dist/types/utils/helpers.d.ts +2 -1
- package/dist/types/utils/interfaces.d.ts +34 -0
- package/package.json +3 -3
- package/src/Preferences/PreferencesController.ts +17 -4
- package/src/utils/constants.ts +3 -3
- package/src/utils/helpers.ts +59 -4
- package/src/utils/interfaces.ts +35 -0
|
@@ -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,
|
|
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,
|
|
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,
|
|
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.
|
|
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: "
|
|
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.
|
|
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
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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
|
-
*
|
|
1045
|
+
* normalizes txParams
|
|
1066
1046
|
*/
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
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
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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
|
-
|
|
1111
|
-
|
|
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
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
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
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
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
|
-
|
|
1216
|
-
|
|
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
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1332
|
-
|
|
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
|
-
*
|
|
1163
|
+
* Validates the given tx parameters
|
|
1164
|
+
* @throws if the tx params contains invalid fields
|
|
1373
1165
|
*/
|
|
1374
|
-
|
|
1375
|
-
let
|
|
1376
|
-
|
|
1377
|
-
|
|
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 (
|
|
1412
|
-
|
|
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
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
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
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
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
|
-
|
|
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
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
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: [
|
|
5022
|
+
params: [address, block.idempotencyKey]
|
|
4875
5023
|
});
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
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
|
-
|
|
4891
|
-
const
|
|
4892
|
-
const
|
|
4893
|
-
return
|
|
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
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
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
|
-
|
|
4910
|
-
const
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
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
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
5062
|
+
name: "local",
|
|
5063
|
+
nonce: highest,
|
|
5064
|
+
details: {
|
|
5065
|
+
startPoint,
|
|
5066
|
+
highest
|
|
5067
|
+
}
|
|
4933
5068
|
};
|
|
4934
5069
|
}
|
|
5070
|
+
}
|
|
4935
5071
|
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
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
|
-
|
|
5101
|
+
checks the network for signed txs and releases the nonce global lock if it is
|
|
4956
5102
|
*/
|
|
4957
|
-
async
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
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
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
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
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
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
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
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
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
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
|
-
|
|
5162
|
-
|
|
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
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
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
|
-
|
|
5170
|
-
|
|
5171
|
-
//
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
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
|
-
|
|
5209
|
-
default:
|
|
5210
|
-
ensureFieldIsString(txParams, key);
|
|
5311
|
+
};
|
|
5211
5312
|
}
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
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
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
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
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
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
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
const
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
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
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
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
|