import { Injectable } from '@angular/core' ; import { BehaviorSubject , from , Observable , of , timer } from 'rxjs' ; import { catchError , map , switchMap , timeout } from 'rxjs/operators' ; import { PolygonGasResponse } from 'src/app/core/services/gas-service/models/polygon-gas-response' ; import { BLOCKCHAIN_NAME } from '@shared/models/blockchain/blockchain-name' ; import BigNumber from 'bignumber.js' ; import { HttpClient } from '@angular/common/http' ; import { Cacheable } from 'ts-cacheable' ; import { PublicBlockchainAdapterService } from '@core/services/blockchain/blockchain-adapters/public-blockchain-adapter.service' ; const supportedBlockchains = [ BLOCKCHAIN_NAME . ETHEREUM , BLOCKCHAIN_NAME . BINANCE_SMART_CHAIN , BLOCKCHAIN_NAME . POLYGON , BLOCKCHAIN_NAME . AVALANCHE ] as const ; type SupportedBlockchain = typeof supportedBlockchains [ number ] ; type NetworksGasPrice < T > = Record < SupportedBlockchain , T > ; @ Injectable ( { providedIn : 'root' } ) export class GasService { /**
     * Gas price request interval in seconds.
     */ private static readonly requestInterval = 15000 ; /**
     * Gas price functions for different networks.
     */ private readonly gasPriceFunctions : NetworksGasPrice < ( ) => Observable < number | null > > ; /**
     * Gas price in Gwei subject.
     */ private readonly networkGasPrice$ : NetworksGasPrice < BehaviorSubject < number | null > > ; /**
     * Gas price update interval in seconds.
     */ private readonly updateInterval : number ; private static isSupportedBlockchain ( blockchain ) { return supportedBlockchains . some ( supBlockchain => supBlockchain === blockchain ) ; } constructor ( private readonly httpClient , private readonly publicBlockchainAdapterService ) { this . updateInterval = 15000 ; this . networkGasPrice$ = { [ BLOCKCHAIN_NAME . ETHEREUM ] : new BehaviorSubject ( null ) , [ BLOCKCHAIN_NAME . BINANCE_SMART_CHAIN ] : new BehaviorSubject ( null ) , [ BLOCKCHAIN_NAME . POLYGON ] : new BehaviorSubject ( null ) , [ BLOCKCHAIN_NAME . AVALANCHE ] : new BehaviorSubject ( null ) } ; this . gasPriceFunctions = { [ BLOCKCHAIN_NAME . ETHEREUM ] : this . fetchEthGas . bind ( this ) , [ BLOCKCHAIN_NAME . BINANCE_SMART_CHAIN ] : this . fetchBscGas . bind ( this ) , [ BLOCKCHAIN_NAME . POLYGON ] : this . fetchPolygonGas . bind ( this ) , [ BLOCKCHAIN_NAME . AVALANCHE ] : this . fetchAvalancheGas . bind ( this ) } ; this . setIntervalOnGasPriceRefreshing ( ) ; } param blockchain public getGasPrice$ ( blockchain ) { if ( ! GasService . isSupportedBlockchain ( blockchain ) ) { throw Error ( 'Not supported blockchain' ) ; } return this . networkGasPrice$ [ blockchain ] . asObservable ( ) ; } param blockchain public async getGasPriceInEthUnits ( blockchain ) { if ( ! GasService . isSupportedBlockchain ( blockchain ) ) { throw Error ( 'Not supported blockchain' ) ; } return new BigNumber ( await this . gasPriceFunctions [ blockchain ] ( ) . toPromise ( ) ) . dividedBy ( 10 ** 9 ) ; } /**
     * Updates gas price in interval.
     */ private setIntervalOnGasPriceRefreshing ( ) { const timer$ = timer ( 0 , this . updateInterval ) ; timer$ . pipe ( switchMap ( ( ) => { return this . gasPriceFunctions [ BLOCKCHAIN_NAME . ETHEREUM ] ( ) ; } ) ) . subscribe ( ( ethGasPrice ) => { if ( ethGasPrice ) { this . networkGasPrice$ [ BLOCKCHAIN_NAME . ETHEREUM ] . next ( ethGasPrice ) ; } } ) ; } return @ Cacheable ( { maxAge : GasService . requestInterval } ) private fetchEthGas ( ) { const requestTimeout = 2000 ; return this . httpClient . get ( 'https://gas-price-api.1inch.io/v1.2/1' ) . pipe ( timeout ( requestTimeout ) , map ( ( response ) => new BigNumber ( response . medium . maxFeePerGas ) . dividedBy ( 10 ** 9 ) . dp ( 0 ) . toNumber ( ) ) , catchError ( ( ) => this . httpClient . get ( 'https://ethgasstation.info/api/ethgasAPI.json' ) . pipe ( timeout ( requestTimeout ) , map ( ( response ) => response . average / 10 ) ) ) , catchError ( ( ) => of ( null ) ) ) ; } return @ Cacheable ( { maxAge : GasService . requestInterval } ) private fetchBscGas ( ) { return of ( 5 ) ; } return @ Cacheable ( { maxAge : GasService . requestInterval } ) private fetchPolygonGas ( ) { return this . httpClient . get ( 'https://gasstation-mainnet.matic.network/' ) . pipe ( map ( ( el ) => Math . floor ( el . standard ) ) , catchError ( ( ) => of ( null ) ) ) ; } return @ Cacheable ( { maxAge : GasService . requestInterval } ) private fetchAvalancheGas ( ) { const blockchainAdapter = this . publicBlockchainAdapterService [ BLOCKCHAIN_NAME . AVALANCHE ] ; return from ( blockchainAdapter . getGasPrice ( ) ) . pipe ( map ( gasPriceInWei => { return new BigNumber ( gasPriceInWei ) . dividedBy ( 10 ** 9 ) . toNumber ( ) ; } ) ) ; } }