import * as request from 'request-promise-native' ; import { BigNumber } from 'bignumber.js' ; import Web3 from 'web3' ; import { Logger , DelegateLogger } from '@faast/ts-common' ; import type { TransactionConfig } from 'web3-core' ; import { AutoFeeLevels } from '@bitaccess/coinlib-common' ; import { DEFAULT_GAS_PRICE_IN_WEI , GAS_STATION_URL , GAS_STATION_FEE_SPEED , MAXIMUM_GAS , GAS_ESTIMATE_MULTIPLIER , ETHEREUM_TRANSFER_COST , } from './constants' ; import { EthTxType } from './types' ; import { retryIfDisconnected } from './utils' ; type Eth = Web3 [ 'eth' ] ; export class NetworkData { private gasStationUrl : string | undefined ; private parityUrl : string | undefined ; private eth : Eth ; private logger : Logger ; constructor ( eth , gasStationUrl = GAS_STATION_URL , parityUrl ? , logger ? ) { this . eth = eth ; this . gasStationUrl = gasStationUrl ; this . parityUrl = parityUrl ; this . logger = new DelegateLogger ( logger , 'NetworkData' ) ; } async getNetworkData ( txType , speed , from , to , data ? ) { const pricePerGasUnit = await this . getGasPrice ( speed ) ; const nonce = await this . getNonce ( from ) ; const amountOfGas = await this . estimateGas ( { from , to , data } , txType ) ; return { pricePerGasUnit , amountOfGas , nonce , } ; } async getNonce ( address ) { const web3Nonce = await this . getWeb3Nonce ( address ) || '0' ; const parityNonce = await this . getParityNonce ( address ) || '0' ; const nonce = BigNumber . maximum ( web3Nonce , parityNonce ) ; return nonce . toNumber ( ) ? nonce . toString ( ) : '0' ; } async getGasPrice ( speed ) { let gasPrice = await this . getGasStationGasPrice ( speed ) ; if ( gasPrice ) return gasPrice ; gasPrice = await this . getWeb3GasPrice ( ) ; if ( gasPrice ) return gasPrice ; return DEFAULT_GAS_PRICE_IN_WEI ; } async estimateGas ( txObject , txType ) { try { let gas = await this . _retryDced ( ( ) => this . eth . estimateGas ( { ... txObject } ) ) ; if ( gas > 21000 ) { gas = gas * GAS_ESTIMATE_MULTIPLIER ; } const maxGas = MAXIMUM_GAS [ txType ] ; if ( gas > maxGas ) { gas = maxGas ; } const result = Math . ceil ( gas ) ; this . logger . debug ( `Estimated gas limit of ${ result } for ${ txType }` ) ; return result ; } catch ( e ) { this . logger . warn ( `Failed to estimate gas for ${ txType } -- ${ e }` ) ; return MAXIMUM_GAS [ txType ] ; } } private async getWeb3Nonce ( address ) { try { const nonce = await this . _retryDced ( ( ) => this . eth . getTransactionCount ( address , 'pending' ) ) ; return ( new BigNumber ( nonce ) ) . toString ( ) ; } catch ( e ) { return '' ; } } private async getParityNonce ( address ) { const data = { method : 'parity_nextNonce' , params : [ address ] , id : 1 , jsonrpc : '2.0' } ; const options = { url : this . parityUrl || '' , json : data } ; let body ; try { body = await request . post ( options ) ; } catch ( e ) { this . logger . warn ( 'Failed to retrieve nonce from parity - ' , e . toString ( ) ) ; return '' ; } if ( ! body || ! body . result ) { this . logger . warn ( 'Bad result or missing fields in parity nextNonce response' , body ) ; return '' ; } return ( new BigNumber ( body . result , 16 ) ) . toString ( ) ; } private async getGasStationGasPrice ( level ) { const hasKey = /\?api-key=/ . test ( this . gasStationUrl || '' ) ; const options = { url : hasKey ? `${ this . gasStationUrl }` : `${ this . gasStationUrl }/json/ethgasAPI.json` , json : true , timeout : 5000 } ; let body ; try { body = await this . _retryDced ( ( ) => request . get ( options ) ) ; } catch ( e ) { this . logger . warn ( 'Failed to retrieve gas price from ethgasstation - ' , e . toString ( ) ) ; return '' ; } const speed = GAS_STATION_FEE_SPEED [ level ] ; if ( ! ( body && body . blockNum && body [ speed ] ) ) { this . logger . warn ( 'Bad result or missing fields in ethgasstation response' , body ) ; return '' ; } const price10xGwei = body [ speed ] ; const gwei = new BigNumber ( price10xGwei ) . dividedBy ( 10 ) ; this . logger . log ( `Retrieved gas price of ${ gwei } Gwei from ethgasstation using speed ${ speed }` ) ; return gwei . multipliedBy ( 1e9 ) . dp ( 0 , BigNumber . ROUND_DOWN ) . toFixed ( ) ; } private async getWeb3GasPrice ( ) { try { const wei = new BigNumber ( await this . _retryDced ( ( ) => this . eth . getGasPrice ( ) ) ) ; this . logger . log ( `Retrieved gas price of ${ wei . div ( 1e9 ) } Gwei from web3` ) ; return wei . dp ( 0 , BigNumber . ROUND_DOWN ) . toFixed ( ) ; } catch ( e ) { this . logger . warn ( 'Failed to retrieve gas price from web3 - ' , e . toString ( ) ) ; return '' ; } } async _retryDced < T > ( fn ) { return retryIfDisconnected ( fn , this . logger ) ; } }