UNPKG

@thorwallet/xchain-ethereum

Version:
395 lines 14.2 kB
"use strict"; 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