@toruslabs/ethereum-controllers 5.3.4 → 5.3.5
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 +614 -554
- package/dist/ethereumControllers.cjs.js.map +1 -1
- package/dist/ethereumControllers.esm.js +1116 -1060
- 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 +2 -2
- package/src/Preferences/PreferencesController.ts +16 -3
- 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
|
});
|
|
@@ -3902,10 +4279,15 @@ class PreferencesController extends BasePreferencesController {
|
|
|
3902
4279
|
chainId
|
|
3903
4280
|
} = this.getProviderConfig();
|
|
3904
4281
|
if (ETHERSCAN_SUPPORTED_CHAINS.includes(chainId)) {
|
|
3905
|
-
|
|
4282
|
+
const etherscanTxn = await this.fetchEtherscanTx({
|
|
3906
4283
|
selectedAddress,
|
|
3907
4284
|
chainId: this.getProviderConfig().chainId
|
|
3908
4285
|
});
|
|
4286
|
+
const finalEthScanTxn = await addEtherscanTransactions(etherscanTxn, selectedAddress, this.provider, chainId);
|
|
4287
|
+
this.updateState({
|
|
4288
|
+
etherscanTransactions: finalEthScanTxn
|
|
4289
|
+
});
|
|
4290
|
+
return etherscanTxn;
|
|
3909
4291
|
}
|
|
3910
4292
|
}
|
|
3911
4293
|
}
|
|
@@ -3914,6 +4296,7 @@ class PreferencesController extends BasePreferencesController {
|
|
|
3914
4296
|
const url = new URL(`${this.config.api}/etherscan`);
|
|
3915
4297
|
url.searchParams.append("chainId", parameters.chainId);
|
|
3916
4298
|
const response = await get(url.href, this.headers(parameters.selectedAddress));
|
|
4299
|
+
log.info("Etherscan Response", response);
|
|
3917
4300
|
return response.success ? response.data : [];
|
|
3918
4301
|
} catch (error) {
|
|
3919
4302
|
log.error("unable to fetch etherscan tx", error);
|
|
@@ -4624,712 +5007,385 @@ class NonceTracker {
|
|
|
4624
5007
|
let mutex = this.lockMap[lockId];
|
|
4625
5008
|
if (!mutex) {
|
|
4626
5009
|
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
|
-
});
|
|
5010
|
+
this.lockMap[lockId] = mutex;
|
|
4862
5011
|
}
|
|
5012
|
+
return mutex;
|
|
4863
5013
|
}
|
|
4864
|
-
async
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
}
|
|
4871
|
-
} = txMeta;
|
|
4872
|
-
const networkNextNonce = await this.provider.request({
|
|
5014
|
+
async _getNetworkNextNonce(address) {
|
|
5015
|
+
// calculate next nonce
|
|
5016
|
+
// we need to make sure our base count
|
|
5017
|
+
// and pending count are from the same block
|
|
5018
|
+
const block = await this.blockTracker.getLatestBlock();
|
|
5019
|
+
const baseCountStr = await this.provider.request({
|
|
4873
5020
|
method: METHOD_TYPES.ETH_GET_TRANSACTION_COUNT,
|
|
4874
|
-
params: [
|
|
5021
|
+
params: [address, block.idempotencyKey]
|
|
4875
5022
|
});
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
}
|
|
4887
|
-
this.droppedBlocksBufferByHash.delete(txHash);
|
|
4888
|
-
return true;
|
|
5023
|
+
const baseCount = Number.parseInt(baseCountStr, 16);
|
|
5024
|
+
const nonceDetails = {
|
|
5025
|
+
block,
|
|
5026
|
+
baseCount
|
|
5027
|
+
};
|
|
5028
|
+
return {
|
|
5029
|
+
name: "network",
|
|
5030
|
+
nonce: baseCount,
|
|
5031
|
+
details: nonceDetails
|
|
5032
|
+
};
|
|
4889
5033
|
}
|
|
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
|
-
});
|
|
5034
|
+
_getHighestLocallyConfirmed(address) {
|
|
5035
|
+
const confirmedTransactions = this.getConfirmedTransactions(address);
|
|
5036
|
+
const highest = this._getHighestNonce(confirmedTransactions);
|
|
5037
|
+
return Number.isInteger(highest) ? highest + 1 : 0;
|
|
4899
5038
|
}
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
5039
|
+
_getHighestNonce(txList) {
|
|
5040
|
+
const nonces = txList.map(txMeta => {
|
|
5041
|
+
const {
|
|
5042
|
+
nonce
|
|
5043
|
+
} = txMeta.transaction;
|
|
5044
|
+
return Number.parseInt(nonce, 16);
|
|
5045
|
+
});
|
|
5046
|
+
const highestNonce = Math.max.apply(null, nonces);
|
|
5047
|
+
return highestNonce;
|
|
4908
5048
|
}
|
|
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
|
-
};
|
|
5049
|
+
_getHighestContinuousFrom(txList, startPoint) {
|
|
5050
|
+
const nonces = new Set(txList.map(txMeta => {
|
|
5051
|
+
const {
|
|
5052
|
+
nonce
|
|
5053
|
+
} = txMeta.transaction;
|
|
5054
|
+
return Number.parseInt(nonce, 16);
|
|
5055
|
+
}));
|
|
5056
|
+
let highest = startPoint;
|
|
5057
|
+
while (nonces.has(highest)) {
|
|
5058
|
+
highest += 1;
|
|
4928
5059
|
}
|
|
4929
5060
|
return {
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
5061
|
+
name: "local",
|
|
5062
|
+
nonce: highest,
|
|
5063
|
+
details: {
|
|
5064
|
+
startPoint,
|
|
5065
|
+
highest
|
|
5066
|
+
}
|
|
4933
5067
|
};
|
|
4934
5068
|
}
|
|
5069
|
+
}
|
|
4935
5070
|
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
5071
|
+
class PendingTransactionTracker extends SafeEventEmitter {
|
|
5072
|
+
constructor(_ref) {
|
|
5073
|
+
let {
|
|
5074
|
+
provider,
|
|
5075
|
+
nonceTracker,
|
|
5076
|
+
approveTransaction,
|
|
5077
|
+
publishTransaction,
|
|
5078
|
+
getPendingTransactions,
|
|
5079
|
+
getConfirmedTransactions
|
|
5080
|
+
} = _ref;
|
|
5081
|
+
super();
|
|
5082
|
+
_defineProperty(this, "DROPPED_BUFFER_COUNT", 3);
|
|
5083
|
+
_defineProperty(this, "nonceTracker", void 0);
|
|
5084
|
+
_defineProperty(this, "provider", void 0);
|
|
5085
|
+
_defineProperty(this, "approveTransaction", void 0);
|
|
5086
|
+
_defineProperty(this, "droppedBlocksBufferByHash", void 0);
|
|
5087
|
+
_defineProperty(this, "getConfirmedTransactions", void 0);
|
|
5088
|
+
_defineProperty(this, "getPendingTransactions", void 0);
|
|
5089
|
+
_defineProperty(this, "publishTransaction", void 0);
|
|
5090
|
+
this.provider = provider;
|
|
5091
|
+
this.nonceTracker = nonceTracker;
|
|
5092
|
+
this.approveTransaction = approveTransaction;
|
|
5093
|
+
this.publishTransaction = publishTransaction;
|
|
5094
|
+
this.getPendingTransactions = getPendingTransactions;
|
|
5095
|
+
this.getConfirmedTransactions = getConfirmedTransactions;
|
|
5096
|
+
this.droppedBlocksBufferByHash = new Map();
|
|
4952
5097
|
}
|
|
4953
5098
|
|
|
4954
5099
|
/**
|
|
4955
|
-
|
|
5100
|
+
checks the network for signed txs and releases the nonce global lock if it is
|
|
4956
5101
|
*/
|
|
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;
|
|
5102
|
+
async updatePendingTxs() {
|
|
5103
|
+
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
|
5104
|
+
const nonceGlobalLock = await this.nonceTracker.getGlobalLock();
|
|
5105
|
+
try {
|
|
5106
|
+
const pendingTxs = this.getPendingTransactions();
|
|
5107
|
+
await Promise.all(pendingTxs.map(txMeta => this._checkPendingTx(txMeta)));
|
|
5108
|
+
} catch (error) {
|
|
5109
|
+
log.error("PendingTransactionTracker - Error updating pending transactions");
|
|
5110
|
+
log.error(error);
|
|
4989
5111
|
}
|
|
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);
|
|
5112
|
+
nonceGlobalLock.releaseLock();
|
|
5049
5113
|
}
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5114
|
+
async resubmitPendingTxs(block) {
|
|
5115
|
+
const pending = this.getPendingTransactions();
|
|
5116
|
+
// only try resubmitting if their are transactions to resubmit
|
|
5117
|
+
if (pending.length === 0) return;
|
|
5118
|
+
// Keep this as a for loop because we want to wait for each item to be submitted
|
|
5119
|
+
for (const txMeta of pending) {
|
|
5120
|
+
try {
|
|
5121
|
+
await this._resubmitTx(txMeta, block.idempotencyKey);
|
|
5122
|
+
} catch (error) {
|
|
5123
|
+
var _value;
|
|
5124
|
+
/*
|
|
5125
|
+
Dont marked as failed if the error is a "known" transaction warning
|
|
5126
|
+
"there is already a transaction with the same sender-nonce
|
|
5127
|
+
but higher/same gas price"
|
|
5128
|
+
Also don't mark as failed if it has ever been broadcast successfully.
|
|
5129
|
+
A successful broadcast means it may still be mined.
|
|
5130
|
+
*/
|
|
5131
|
+
const errorMessage = ((_value = error.value) === null || _value === void 0 || (_value = _value.message) === null || _value === void 0 ? void 0 : _value.toLowerCase()) || error.message.toLowerCase();
|
|
5132
|
+
const isKnownTx =
|
|
5133
|
+
// geth
|
|
5134
|
+
errorMessage.includes("replacement transaction underpriced") || errorMessage.includes("known transaction") ||
|
|
5135
|
+
// parity
|
|
5136
|
+
errorMessage.includes("gas price too low to replace") || errorMessage.includes("transaction with the same hash was already imported") ||
|
|
5137
|
+
// other
|
|
5138
|
+
errorMessage.includes("gateway timeout") || errorMessage.includes("nonce too low");
|
|
5139
|
+
// ignore resubmit warnings, return early
|
|
5140
|
+
if (isKnownTx) return;
|
|
5141
|
+
// encountered real error - transition to error state
|
|
5142
|
+
txMeta.warning = {
|
|
5143
|
+
error: errorMessage,
|
|
5144
|
+
message: "There was an error when resubmitting this transaction."
|
|
5145
|
+
};
|
|
5146
|
+
this.emit(TX_EVENTS.TX_WARNING, {
|
|
5147
|
+
txMeta,
|
|
5148
|
+
error,
|
|
5149
|
+
txId: txMeta.id
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5055
5153
|
}
|
|
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
|
-
}
|
|
5154
|
+
async _resubmitTx(txMeta, latestBlockNumber) {
|
|
5155
|
+
if (!txMeta.firstRetryBlockNumber) {
|
|
5156
|
+
this.emit(TX_EVENTS.TX_BLOCK_UPDATE, {
|
|
5157
|
+
txMeta,
|
|
5158
|
+
latestBlockNumber,
|
|
5159
|
+
txId: txMeta.id
|
|
5160
|
+
});
|
|
5161
|
+
}
|
|
5162
|
+
const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber;
|
|
5163
|
+
const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16);
|
|
5164
|
+
const retryCount = txMeta.retryCount || 0;
|
|
5069
5165
|
|
|
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
|
-
}
|
|
5166
|
+
// Exponential backoff to limit retries at publishing (capped at last 15 mins)
|
|
5167
|
+
if (txBlockDistance <= Math.min(50, 2 ** retryCount)) return undefined;
|
|
5082
5168
|
|
|
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
|
-
}
|
|
5169
|
+
// Only auto-submit already-signed txs:
|
|
5170
|
+
if (!("rawTx" in txMeta)) return this.approveTransaction(txMeta.id);
|
|
5171
|
+
const {
|
|
5172
|
+
rawTx
|
|
5173
|
+
} = txMeta;
|
|
5174
|
+
const txHash = await this.publishTransaction(rawTx);
|
|
5092
5175
|
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
throw rpcErrors.invalidParams(`Invalid transaction params: ${field} is not a string. got: (${txParams[field]})`);
|
|
5176
|
+
// Increment successful tries:
|
|
5177
|
+
this.emit(TX_EVENTS.TX_RETRY, {
|
|
5178
|
+
txMeta,
|
|
5179
|
+
txId: txMeta.id
|
|
5180
|
+
});
|
|
5181
|
+
return txHash;
|
|
5100
5182
|
}
|
|
5101
|
-
|
|
5183
|
+
async _checkPendingTx(foundTx) {
|
|
5184
|
+
const txMeta = foundTx;
|
|
5185
|
+
const txHash = txMeta.transactionHash;
|
|
5186
|
+
const txId = txMeta.id;
|
|
5102
5187
|
|
|
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
|
-
}
|
|
5188
|
+
// Only check submitted txs
|
|
5189
|
+
if (txMeta.status !== TransactionStatus.submitted) return;
|
|
5123
5190
|
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
}
|
|
5191
|
+
// extra check in case there was an uncaught error during the
|
|
5192
|
+
// signature and submission process
|
|
5193
|
+
if (!txHash) {
|
|
5194
|
+
const noTxHashError = new Error("We had an error while submitting this transaction, please try again.");
|
|
5195
|
+
noTxHashError.name = "NoTxHashError";
|
|
5196
|
+
this.emit(TX_EVENTS.TX_FAILED, {
|
|
5197
|
+
txId,
|
|
5198
|
+
error: noTxHashError
|
|
5199
|
+
});
|
|
5200
|
+
return;
|
|
5201
|
+
}
|
|
5135
5202
|
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5203
|
+
// If another tx with the same nonce is mined, set as failed.
|
|
5204
|
+
if (this._checkIfNonceIsTaken(txMeta)) {
|
|
5205
|
+
this.emit(TX_EVENTS.TX_DROPPED, {
|
|
5206
|
+
txId
|
|
5207
|
+
});
|
|
5208
|
+
return;
|
|
5209
|
+
}
|
|
5210
|
+
try {
|
|
5211
|
+
const transactionReceipt = await this.provider.request({
|
|
5212
|
+
method: METHOD_TYPES.ETH_GET_TRANSACTION_RECEIPT,
|
|
5213
|
+
params: [txHash]
|
|
5214
|
+
});
|
|
5215
|
+
if (transactionReceipt !== null && transactionReceipt !== void 0 && transactionReceipt.blockNumber) {
|
|
5216
|
+
const {
|
|
5217
|
+
baseFeePerGas,
|
|
5218
|
+
timestamp
|
|
5219
|
+
} = await this.provider.request({
|
|
5220
|
+
method: METHOD_TYPES.ETH_GET_BLOCK_BY_HASH,
|
|
5221
|
+
params: [transactionReceipt.blockHash, false]
|
|
5222
|
+
});
|
|
5223
|
+
this.emit(TX_EVENTS.TX_CONFIRMED, {
|
|
5224
|
+
txId,
|
|
5225
|
+
txReceipt: transactionReceipt,
|
|
5226
|
+
baseFeePerGas,
|
|
5227
|
+
blockTimestamp: timestamp
|
|
5228
|
+
});
|
|
5229
|
+
return;
|
|
5230
|
+
}
|
|
5231
|
+
} catch (error) {
|
|
5232
|
+
log.error("error while loading tx", error);
|
|
5233
|
+
txMeta.warning = {
|
|
5234
|
+
error: error.message,
|
|
5235
|
+
message: "There was a problem loading this transaction."
|
|
5236
|
+
};
|
|
5237
|
+
this.emit(TX_EVENTS.TX_WARNING, {
|
|
5238
|
+
txMeta
|
|
5239
|
+
});
|
|
5240
|
+
}
|
|
5241
|
+
if (await this._checkIfTxWasDropped(txMeta)) {
|
|
5242
|
+
this.emit(TX_EVENTS.TX_DROPPED, {
|
|
5243
|
+
txId
|
|
5244
|
+
});
|
|
5145
5245
|
}
|
|
5146
|
-
} else if (txParameters.to !== undefined && !isValidAddress(txParameters.to)) {
|
|
5147
|
-
throw rpcErrors.invalidParams('Invalid "to" address.');
|
|
5148
5246
|
}
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5247
|
+
async _checkIfTxWasDropped(txMeta) {
|
|
5248
|
+
const {
|
|
5249
|
+
transactionHash: txHash,
|
|
5250
|
+
transaction: {
|
|
5251
|
+
nonce,
|
|
5252
|
+
from
|
|
5253
|
+
}
|
|
5254
|
+
} = txMeta;
|
|
5255
|
+
const networkNextNonce = await this.provider.request({
|
|
5256
|
+
method: METHOD_TYPES.ETH_GET_TRANSACTION_COUNT,
|
|
5257
|
+
params: [from, "latest"]
|
|
5258
|
+
});
|
|
5259
|
+
if (Number.parseInt(nonce, 16) >= Number.parseInt(networkNextNonce, 16)) {
|
|
5260
|
+
return false;
|
|
5261
|
+
}
|
|
5262
|
+
if (!this.droppedBlocksBufferByHash.has(txHash)) {
|
|
5263
|
+
this.droppedBlocksBufferByHash.set(txHash, 0);
|
|
5264
|
+
}
|
|
5265
|
+
const currentBlockBuffer = this.droppedBlocksBufferByHash.get(txHash);
|
|
5266
|
+
if (currentBlockBuffer < this.DROPPED_BUFFER_COUNT) {
|
|
5267
|
+
this.droppedBlocksBufferByHash.set(txHash, currentBlockBuffer + 1);
|
|
5268
|
+
return false;
|
|
5269
|
+
}
|
|
5270
|
+
this.droppedBlocksBufferByHash.delete(txHash);
|
|
5271
|
+
return true;
|
|
5160
5272
|
}
|
|
5161
|
-
|
|
5162
|
-
|
|
5273
|
+
_checkIfNonceIsTaken(txMeta) {
|
|
5274
|
+
const address = txMeta.transaction.from;
|
|
5275
|
+
const completed = this.getConfirmedTransactions(address);
|
|
5276
|
+
return completed.some(otherMeta => {
|
|
5277
|
+
if (otherMeta.id === txMeta.id) {
|
|
5278
|
+
return false;
|
|
5279
|
+
}
|
|
5280
|
+
return otherMeta.transaction.nonce === txMeta.transaction.nonce;
|
|
5281
|
+
});
|
|
5163
5282
|
}
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5283
|
+
}
|
|
5284
|
+
|
|
5285
|
+
class TransactionGasUtil {
|
|
5286
|
+
constructor(provider, blockTracker) {
|
|
5287
|
+
_defineProperty(this, "provider", void 0);
|
|
5288
|
+
_defineProperty(this, "blockTracker", void 0);
|
|
5289
|
+
this.provider = provider;
|
|
5290
|
+
this.blockTracker = blockTracker;
|
|
5168
5291
|
}
|
|
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})`);
|
|
5292
|
+
async analyzeGasUsage(txMeta) {
|
|
5293
|
+
const block = await this.blockTracker.getLatestBlock();
|
|
5294
|
+
// fallback to block gasLimit
|
|
5295
|
+
const blockGasLimitBN = new BN$1(stripHexPrefix(block.gasLimit), 16);
|
|
5296
|
+
const saferGasLimitBN = blockGasLimitBN.mul(new BN$1(19)).div(new BN$1(20));
|
|
5297
|
+
let estimatedGasHex = addHexPrefix(saferGasLimitBN.toString("hex"));
|
|
5298
|
+
let simulationFails;
|
|
5299
|
+
try {
|
|
5300
|
+
estimatedGasHex = await this.estimateTxGas(txMeta);
|
|
5301
|
+
} catch (error) {
|
|
5302
|
+
log.warn(error);
|
|
5303
|
+
simulationFails = {
|
|
5304
|
+
reason: error.message,
|
|
5305
|
+
errorKey: error.errorKey,
|
|
5306
|
+
debug: {
|
|
5307
|
+
blockNumber: block.idempotencyKey,
|
|
5308
|
+
blockGasLimit: block.gasLimit
|
|
5207
5309
|
}
|
|
5208
|
-
|
|
5209
|
-
default:
|
|
5210
|
-
ensureFieldIsString(txParams, key);
|
|
5310
|
+
};
|
|
5211
5311
|
}
|
|
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
|
|
5312
|
+
return {
|
|
5313
|
+
blockGasLimit: block.gasLimit,
|
|
5314
|
+
estimatedGasHex,
|
|
5315
|
+
simulationFails
|
|
5243
5316
|
};
|
|
5244
|
-
} catch {
|
|
5245
|
-
// ignore and next try to parse with erc721 ABI
|
|
5246
5317
|
}
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5318
|
+
|
|
5319
|
+
/**
|
|
5320
|
+
Adds a gas buffer with out exceeding the block gas limit
|
|
5321
|
+
*/
|
|
5322
|
+
addGasBuffer(initialGasLimitHex, blockGasLimitHex) {
|
|
5323
|
+
let multiplier = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1.5;
|
|
5324
|
+
const initialGasLimitBn = new BN$1(stripHexPrefix(initialGasLimitHex), 16);
|
|
5325
|
+
const blockGasLimitBn = new BN$1(stripHexPrefix(blockGasLimitHex), 16);
|
|
5326
|
+
const upperGasLimitBn = blockGasLimitBn.muln(0.9);
|
|
5327
|
+
const bufferedGasLimitBn = initialGasLimitBn.muln(multiplier);
|
|
5328
|
+
|
|
5329
|
+
// if initialGasLimit is above blockGasLimit, dont modify it
|
|
5330
|
+
if (initialGasLimitBn.gt(upperGasLimitBn)) return addHexPrefix(initialGasLimitBn.toString("hex"));
|
|
5331
|
+
// if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit
|
|
5332
|
+
if (bufferedGasLimitBn.lt(upperGasLimitBn)) return addHexPrefix(bufferedGasLimitBn.toString("hex"));
|
|
5333
|
+
// otherwise use blockGasLimit
|
|
5334
|
+
return addHexPrefix(upperGasLimitBn.toString("hex"));
|
|
5258
5335
|
}
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5336
|
+
|
|
5337
|
+
/**
|
|
5338
|
+
Estimates the tx's gas usage
|
|
5339
|
+
*/
|
|
5340
|
+
async estimateTxGas(txMeta) {
|
|
5341
|
+
const txParams = cloneDeep(txMeta.transaction);
|
|
5342
|
+
|
|
5343
|
+
// `eth_estimateGas` can fail if the user has insufficient balance for the
|
|
5344
|
+
// value being sent, or for the gas cost. We don't want to check their
|
|
5345
|
+
// balance here, we just want the gas estimate. The gas price is removed
|
|
5346
|
+
// to skip those balance checks. We check balance elsewhere. We also delete
|
|
5347
|
+
// maxFeePerGas and maxPriorityFeePerGas to support EIP-1559 txs.
|
|
5348
|
+
delete txParams.gasPrice;
|
|
5349
|
+
delete txParams.maxFeePerGas;
|
|
5350
|
+
delete txParams.maxPriorityFeePerGas;
|
|
5351
|
+
return this.provider.request({
|
|
5352
|
+
method: "eth_estimateGas",
|
|
5353
|
+
params: [txParams]
|
|
5262
5354
|
});
|
|
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
5355
|
}
|
|
5271
|
-
return undefined;
|
|
5272
5356
|
}
|
|
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;
|
|
5357
|
+
|
|
5358
|
+
/**
|
|
5359
|
+
Generates an array of history objects sense the previous state.
|
|
5360
|
+
The object has the keys
|
|
5361
|
+
op (the operation performed),
|
|
5362
|
+
path (the key and if a nested object then each key will be seperated with a `/`)
|
|
5363
|
+
value
|
|
5364
|
+
with the first entry having the note and a timestamp when the change took place
|
|
5365
|
+
*/
|
|
5366
|
+
function generateHistoryEntry(previousState, newState, note) {
|
|
5367
|
+
const entry = jsonDiffer.compare(previousState, newState);
|
|
5368
|
+
// Add a note to the first op, since it breaks if we append it to the entry
|
|
5369
|
+
if (entry[0]) {
|
|
5370
|
+
if (note) {
|
|
5371
|
+
entry[0].note = note;
|
|
5325
5372
|
}
|
|
5373
|
+
entry[0].timestamp = Date.now();
|
|
5326
5374
|
}
|
|
5327
|
-
return
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5375
|
+
return entry;
|
|
5376
|
+
}
|
|
5377
|
+
|
|
5378
|
+
/**
|
|
5379
|
+
Recovers previous txMeta state obj
|
|
5380
|
+
*/
|
|
5381
|
+
function replayHistory(_shortHistory) {
|
|
5382
|
+
const shortHistory = cloneDeep(_shortHistory);
|
|
5383
|
+
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument);
|
|
5384
|
+
}
|
|
5385
|
+
function snapshotFromTxMeta(txMeta) {
|
|
5386
|
+
const shallow = _objectSpread({}, txMeta);
|
|
5387
|
+
delete shallow.history;
|
|
5388
|
+
return cloneDeep(shallow);
|
|
5333
5389
|
}
|
|
5334
5390
|
|
|
5335
5391
|
class TransactionStateManager extends BaseTransactionStateManager {
|
|
@@ -6110,5 +6166,5 @@ class TransactionController extends TransactionStateManager {
|
|
|
6110
6166
|
}
|
|
6111
6167
|
}
|
|
6112
6168
|
|
|
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 };
|
|
6169
|
+
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
6170
|
//# sourceMappingURL=ethereumControllers.esm.js.map
|