@thorwallet/xchain-ethereum
Version:
Ethereum client for XChainJS
395 lines • 14.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTokenBalances = exports.getDecimal = exports.filterSelfTxs = exports.getPrefix = exports.getDefaultGasPrices = exports.getDefaultFees = exports.estimateDefaultFeesWithGasPricesAndLimits = exports.getFee = exports.getTxFromEthplorerEthTransaction = exports.getTxFromEthplorerTokenOperation = exports.getTxFromEthTransaction = exports.getTxFromTokenTransaction = exports.validateSymbol = exports.getTokenAddress = exports.validateAddress = exports.ethNetworkToXchains = exports.xchainNetworkToEths = exports.MAX_APPROVAL = exports.ETHAddress = exports.DEFAULT_GAS_PRICE = exports.BASE_TOKEN_GAS_COST = exports.SIMPLE_GAS_COST = exports.ETHPLORER_FREEKEY = exports.ETH_DECIMAL = void 0;
const tslib_1 = require("tslib");
const xchain_util_1 = require("@thorwallet/xchain-util");
const ethers_1 = require("ethers");
const utils_1 = require("ethers/lib/utils");
const types_1 = require("./types");
const erc20_json_1 = tslib_1.__importDefault(require("./data/erc20.json"));
exports.ETH_DECIMAL = 18;
exports.ETHPLORER_FREEKEY = 'freekey';
// from https://github.com/MetaMask/metamask-extension/blob/ee205b893fe61dc4736efc576e0663189a9d23da/ui/app/pages/send/send.constants.js#L39
// and based on recommendations of https://ethgasstation.info/blog/gas-limit/
exports.SIMPLE_GAS_COST = ethers_1.BigNumber.from(21000);
exports.BASE_TOKEN_GAS_COST = ethers_1.BigNumber.from(100000);
// default gas price in gwei
exports.DEFAULT_GAS_PRICE = 50;
exports.ETHAddress = '0x0000000000000000000000000000000000000000';
exports.MAX_APPROVAL = ethers_1.BigNumber.from(2).pow(256).sub(1);
/**
* XChainNetwork -> EthNetwork
*
* @param {XChainNetwork} network
* @returns {EthNetwork}
*/
const xchainNetworkToEths = (network) => {
switch (network) {
// DO NOT use switch/case's default branch
// to be sure that ALL possible cases are
// processed in a similar way to reverted ethNetworkToXchains
case 'mainnet':
return types_1.Network.MAIN;
case 'testnet':
return types_1.Network.TEST;
}
};
exports.xchainNetworkToEths = xchainNetworkToEths;
/**
* EthNetwork -> XChainNetwork
*
* @param {EthNetwork} network
* @returns {XChainNetwork}
*/
const ethNetworkToXchains = (network) => {
switch (network) {
// DO NOT use switch/case's default branch
// to be sure that ALL possible cases are
// processed in a similar way to reverted xchainNetworkToEths
case types_1.Network.MAIN:
return 'mainnet';
case types_1.Network.TEST:
return 'testnet';
}
};
exports.ethNetworkToXchains = ethNetworkToXchains;
/**
* Validate the given address.
*
* @param {Address} address
* @returns {boolean} `true` or `false`
*/
const validateAddress = (address) => {
try {
ethers_1.ethers.utils.getAddress(address);
return true;
}
catch (error) {
return false;
}
};
exports.validateAddress = validateAddress;
/**
* Get token address from asset.
*
* @param {Asset} asset
* @returns {string|null} The token address.
*/
const getTokenAddress = (asset) => {
try {
// strip 0X only - 0x is still valid
return ethers_1.ethers.utils.getAddress(asset.symbol.slice(asset.ticker.length + 1).replace(/^0X/, ''));
}
catch (err) {
return null;
}
};
exports.getTokenAddress = getTokenAddress;
/**
* Check if the symbol is valid.
*
* @param {string|null|undefined} symbol
* @returns {boolean} `true` or `false`.
*/
const validateSymbol = (symbol) => (symbol ? symbol.length >= 3 : false);
exports.validateSymbol = validateSymbol;
/**
* Get transactions from token tx
*
* @param {TokenTransactionInfo} tx
* @returns {Tx|null} The parsed transaction.
*/
const getTxFromTokenTransaction = (tx) => {
const decimals = parseInt(tx.tokenDecimal) || exports.ETH_DECIMAL;
const symbol = tx.tokenSymbol;
const address = tx.contractAddress;
if (exports.validateSymbol(symbol) && exports.validateAddress(address)) {
const tokenAsset = xchain_util_1.assetFromString(`${xchain_util_1.ETHChain}.${symbol}-${address}`);
if (tokenAsset) {
return {
asset: tokenAsset,
from: [
{
from: tx.from,
amount: xchain_util_1.baseAmount(tx.value, decimals),
},
],
to: [
{
to: tx.to,
amount: xchain_util_1.baseAmount(tx.value, decimals),
},
],
date: new Date(parseInt(tx.timeStamp) * 1000),
type: 'transfer',
hash: tx.hash,
ethTokenSymbol: tx.tokenSymbol,
ethTokenName: tx.tokenName,
ethGasPrice: tx.gasPrice,
ethGas: tx.gas,
ethGasUsed: tx.gasUsed,
ethCumulativeGasUsed: tx.cumulativeGasUsed,
confirmations: Number(tx.confirmations),
binanceFee: null,
memo: null,
};
}
}
return null;
};
exports.getTxFromTokenTransaction = getTxFromTokenTransaction;
/**
* Get transactions from ETH transaction
*
* @param {ETHTransactionInfo} tx
* @returns {Tx} The parsed transaction.
*/
const getTxFromEthTransaction = (tx) => {
return {
asset: xchain_util_1.AssetETH,
from: [
{
from: tx.from,
amount: xchain_util_1.baseAmount(tx.value, exports.ETH_DECIMAL),
},
],
to: [
{
to: tx.to,
amount: xchain_util_1.baseAmount(tx.value, exports.ETH_DECIMAL),
},
],
date: new Date(parseInt(tx.timeStamp) * 1000),
type: 'transfer',
hash: tx.hash,
confirmations: null,
binanceFee: null,
ethCumulativeGasUsed: null,
ethGasUsed: tx.gasUsed,
ethGas: tx.gas,
ethGasPrice: tx.gasPrice,
ethTokenName: null,
ethTokenSymbol: null,
memo: null,
};
};
exports.getTxFromEthTransaction = getTxFromEthTransaction;
/**
* Get transactions from operation
*
* @param {TransactionOperation} operation
* @returns {Tx|null} The parsed transaction.
*/
const getTxFromEthplorerTokenOperation = (operation) => {
const decimals = parseInt(operation.tokenInfo.decimals) || exports.ETH_DECIMAL;
const { symbol, address } = operation.tokenInfo;
if (exports.validateSymbol(symbol) && exports.validateAddress(address)) {
const tokenAsset = xchain_util_1.assetFromString(`${xchain_util_1.ETHChain}.${symbol}-${address}`);
if (tokenAsset) {
return {
asset: tokenAsset,
from: [
{
from: operation.from,
amount: xchain_util_1.baseAmount(operation.value, decimals),
},
],
to: [
{
to: operation.to,
amount: xchain_util_1.baseAmount(operation.value, decimals),
},
],
date: new Date(operation.timestamp * 1000),
type: operation.type === 'transfer' ? 'transfer' : 'unknown',
hash: operation.transactionHash,
confirmations: null,
binanceFee: null,
ethCumulativeGasUsed: null,
ethGas: null,
ethGasPrice: null,
ethGasUsed: null,
ethTokenName: null,
ethTokenSymbol: null,
memo: null,
};
}
}
return null;
};
exports.getTxFromEthplorerTokenOperation = getTxFromEthplorerTokenOperation;
/**
* Get transactions from ETH transaction
*
* @param {TransactionInfo} txInfo
* @returns {Tx} The parsed transaction.
*/
const getTxFromEthplorerEthTransaction = (txInfo) => {
var _a;
return {
asset: xchain_util_1.AssetETH,
from: [
{
from: txInfo.from,
amount: xchain_util_1.assetToBase(xchain_util_1.assetAmount(txInfo.value, exports.ETH_DECIMAL)),
},
],
to: [
{
to: txInfo.to,
amount: xchain_util_1.assetToBase(xchain_util_1.assetAmount(txInfo.value, exports.ETH_DECIMAL)),
},
],
date: new Date(txInfo.timestamp * 1000),
type: 'transfer',
hash: txInfo.hash,
confirmations: (_a = txInfo.confirmations) !== null && _a !== void 0 ? _a : null,
binanceFee: null,
ethCumulativeGasUsed: null,
ethGas: null,
ethGasUsed: String(txInfo.gasUsed),
ethGasPrice: null,
ethTokenName: null,
ethTokenSymbol: null,
memo: null,
};
};
exports.getTxFromEthplorerEthTransaction = getTxFromEthplorerEthTransaction;
/**
* Calculate fees by multiplying .
*
* @returns {Fees} The default gas price.
*/
const getFee = ({ gasPrice, gasLimit }) => xchain_util_1.baseAmount(gasPrice.amount().multipliedBy(gasLimit.toString()), exports.ETH_DECIMAL);
exports.getFee = getFee;
const estimateDefaultFeesWithGasPricesAndLimits = (asset) => {
const gasPrices = {
average: xchain_util_1.baseAmount(utils_1.parseUnits(exports.DEFAULT_GAS_PRICE.toString(), 'gwei').toString(), exports.ETH_DECIMAL),
fast: xchain_util_1.baseAmount(utils_1.parseUnits((exports.DEFAULT_GAS_PRICE * 2).toString(), 'gwei').toString(), exports.ETH_DECIMAL),
fastest: xchain_util_1.baseAmount(utils_1.parseUnits((exports.DEFAULT_GAS_PRICE * 3).toString(), 'gwei').toString(), exports.ETH_DECIMAL),
};
const { fast: fastGP, fastest: fastestGP, average: averageGP } = gasPrices;
let assetAddress;
if (asset && xchain_util_1.assetToString(asset) !== xchain_util_1.assetToString(xchain_util_1.AssetETH)) {
assetAddress = exports.getTokenAddress(asset);
}
let gasLimit;
if (assetAddress && assetAddress !== exports.ETHAddress) {
gasLimit = ethers_1.BigNumber.from(exports.BASE_TOKEN_GAS_COST);
}
else {
gasLimit = ethers_1.BigNumber.from(exports.SIMPLE_GAS_COST);
}
return {
gasPrices,
gasLimit,
fees: {
type: 'byte',
average: exports.getFee({ gasPrice: averageGP, gasLimit }),
fast: exports.getFee({ gasPrice: fastGP, gasLimit }),
fastest: exports.getFee({ gasPrice: fastestGP, gasLimit }),
},
};
};
exports.estimateDefaultFeesWithGasPricesAndLimits = estimateDefaultFeesWithGasPricesAndLimits;
/**
* Get the default fees.
*
* @returns {Fees} The default gas price.
*/
const getDefaultFees = (asset) => {
const { fees } = exports.estimateDefaultFeesWithGasPricesAndLimits(asset);
return fees;
};
exports.getDefaultFees = getDefaultFees;
/**
* Get the default gas price.
*
* @returns {Fees} The default gas prices.
*/
const getDefaultGasPrices = (asset) => {
const { gasPrices } = exports.estimateDefaultFeesWithGasPricesAndLimits(asset);
return gasPrices;
};
exports.getDefaultGasPrices = getDefaultGasPrices;
/**
* Get address prefix based on the network.
*
* @returns {string} The address prefix based on the network.
*
**/
const getPrefix = () => '0x';
exports.getPrefix = getPrefix;
/**
* Filter self txs
*
* @returns {T[]}
*
**/
const filterSelfTxs = (txs) => {
const filterTxs = txs.filter((tx) => tx.from !== tx.to);
let selfTxs = txs.filter((tx) => tx.from === tx.to);
while (selfTxs.length) {
const selfTx = selfTxs[0];
filterTxs.push(selfTx);
selfTxs = selfTxs.filter((tx) => tx.hash !== selfTx.hash);
}
return filterTxs;
};
exports.filterSelfTxs = filterSelfTxs;
/**
* Get Decimals
*
* @param {Asset} asset
* @returns {Number} the decimal of a given asset
*
* @throws {"Invalid asset"} Thrown if the given asset is invalid
* @throws {"Invalid provider"} Thrown if the given provider is invalid
*/
const getDecimal = (asset, provider) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
if (xchain_util_1.assetToString(asset) === xchain_util_1.assetToString(xchain_util_1.AssetETH)) {
return Promise.resolve(exports.ETH_DECIMAL);
}
const assetAddress = exports.getTokenAddress(asset);
if (!assetAddress) {
throw new Error(`Invalid asset ${xchain_util_1.assetToString(asset)}`);
}
try {
const contract = new ethers_1.ethers.Contract(assetAddress, erc20_json_1.default, provider);
const decimal = yield contract.decimals();
return ethers_1.ethers.BigNumber.from(decimal).toNumber();
}
catch (err) {
throw new Error(`Invalid provider: ${err}`);
}
});
exports.getDecimal = getDecimal;
/**
* Get Token Balances
*
* @param {Array<TokenBalance>} tokenBalances
* @returns {Array<Balance>} the parsed balances
*
*/
const getTokenBalances = (tokenBalances) => {
return tokenBalances.reduce((acc, cur) => {
var _a;
const { symbol, address: tokenAddress } = cur.tokenInfo;
if (exports.validateSymbol(symbol) && exports.validateAddress(tokenAddress) && ((_a = cur === null || cur === void 0 ? void 0 : cur.tokenInfo) === null || _a === void 0 ? void 0 : _a.decimals) !== undefined) {
const decimals = parseInt(cur.tokenInfo.decimals, 10);
const tokenAsset = xchain_util_1.assetFromString(`${xchain_util_1.ETHChain}.${symbol}-${ethers_1.ethers.utils.getAddress(tokenAddress)}`);
if (tokenAsset) {
return [
...acc,
{
asset: tokenAsset,
amount: xchain_util_1.baseAmount(cur.balance, decimals),
},
];
}
}
return acc;
}, []);
};
exports.getTokenBalances = getTokenBalances;
//# sourceMappingURL=utils.js.map