import { Injectable , Inject , PLATFORM_ID } from '@angular/core' ; import { Web3Provider , ExternalProvider } from '@ethersproject/providers' ; import { BigNumber , BigNumberish , Contract , utils , Wallet } from 'ethers' ; import { Web3ModalService } from '@mindsorg/web3modal-angular' ; import asyncSleep from '../../helpers/async-sleep' ; import { TransactionOverlayService } from './transaction-overlay/transaction-overlay.service' ; import { ConfigsService } from '../../common/services/configs.service' ; import { defaultAbiCoder , Interface } from 'ethers/lib/utils' ; import { FormToastService } from '../../common/services/form-toast.service' ; type Address = string ; @ Injectable ( ) export class Web3WalletService { public config ; public provider : Web3Provider | null = null ; protected unavailable : boolean = false ; protected local : boolean = false ; protected _ready : Promise < any > ; protected _web3LoadAttempt : number = 0 ; constructor ( protected transactionOverlay , @ Inject ( PLATFORM_ID ) private platformId , private configs , private web3modalService , private toast ) { this . config = this . configs . get ( 'blockchain' ) ; } async initializeProvider ( ) { if ( ! this . provider ) { const provider = await this . web3modalService . open ( ) ; this . setProvider ( provider ) ; } return this . provider ; } getSigner ( ) { if ( this . provider ) { return this . provider . getSigner ( ) ; } return null ; } resetProvider ( ) { this . provider = null ; this . web3modalService . clearCachedProvider ( ) ; } getABIInterface ( abi ) { return new Interface ( abi ) ; } setProvider ( provider ) { this . provider = new Web3Provider ( provider ) ; } async getWallets ( ) { const address = await this . getCurrentWallet ( ) ; if ( ! address ) { return [ ] ; } return [ address ] ; } async getCurrentWallet ( forceAuthorization = false ) { if ( forceAuthorization ) { await this . initializeProvider ( ) ; } const signer = this . getSigner ( ) ; if ( ! signer ) { return false ; } return await signer . getAddress ( ) ; } async getBalance ( ) { const signer = this . getSigner ( ) ; if ( ! signer ) { return false ; } const balance = await signer . getBalance ( ) ; return balance . toString ( ) ; } async isLocked ( ) { return ! ( await this . getCurrentWallet ( ) ) ; } async unlock ( ) { if ( await this . isLocked ( ) ) { try { await this . getCurrentWallet ( true ) ; } catch ( e ) { console . log ( e ) ; } } return ! ( await this . isLocked ( ) ) ; } async isSameNetwork ( ) { const provider = this . provider ; let chainId = null ; if ( provider ) { const network = await provider . getNetwork ( ) ; chainId = network . chainId ; } return ( chainId || 1 ) == this . config . client_network ; } setUp ( ) { this . config = this . configs . get ( 'blockchain' ) ; } isUnavailable ( ) { return this . unavailable ; } async sendSignedContractMethodWithValue ( contract , method , params , value , message = '' ) { const connectedContract = contract . connect ( this . getSigner ( ) ) ; let gasLimit ; try { gasLimit = ( await connectedContract . estimateGas [ method ] ( ... params , { value } ) ) . toHexString ( ) ; } catch ( e ) { this . handleEstimateGasError ( e ) ; gasLimit = BigNumber . from ( 15000000 ) . toHexString ( ) ; } /**
         * Dispatch an EIP-2718 transaction for EIP-1559 compatibility.
         * ethers.js should fallback to legacy transactions if
         * the web3 provider is not equipped to deal with this tx type.
         *
         * maxFeePerGas and maxPriorityFeePerGas are omitted and left for
         * the web3 client to decide.
         */ const txHash = await this . transactionOverlay . waitForExternalTx ( ( ) => connectedContract [ method ] ( ... params , { type : 2 , value : value , gasLimit : gasLimit , } ) , message ) ; await asyncSleep ( 1000 ) ; return txHash ; } async sendSignedContractMethod ( contract , method , params , message = '' ) { return await this . sendSignedContractMethodWithValue ( contract , method , params , 0 , message ) ; } async sendTransaction ( originalTxObject , message = '' ) { if ( ! originalTxObject . gasLimit ) { try { const gasLimit = await this . getSigner ( ) . estimateGas ( originalTxObject ) ; originalTxObject . gasLimit = gasLimit . toHexString ( ) ; } catch ( e ) { console . log ( e ) ; originalTxObject . gasLimit = 15000000 ; } } const txHash = await this . transactionOverlay . waitForExternalTx ( ( ) => this . getSigner ( ) . sendTransaction ( originalTxObject ) , message ) ; await asyncSleep ( 1000 ) ; return txHash ; } getContract ( address , abi ) { return new Contract ( address , abi ) ; } toWei ( amount , unit ? ) { const weiAmount = utils . parseUnits ( amount . toString ( ) , unit ) . toString ( ) ; return BigNumber . from ( weiAmount ) . toHexString ( ) ; } fromWei ( amount , unit ? ) { return utils . formatUnits ( amount . toString ( ) , unit ) . toString ( ) ; } encodeParams ( types , values ) { return defaultAbiCoder . encode ( types , values ) ; } privateKeyToAccount ( privateKey ) { if ( typeof privateKey === 'string' ) { if ( privateKey . indexOf ( '0x' ) !== 0 ) { return new Wallet ( '0x' + privateKey ) . address ; } } return new Wallet ( privateKey ) . address ; } getOnChainInterfaceLabel ( ) { if ( this . local ) { return 'Private Key' ; } if ( window . web3 . currentProvider . constructor . name === 'MetamaskInpageProvider' ) { return 'Metamask' ; } else if ( window . web3 . currentProvider . constructor . name === 'EthereumProvider' ) { return 'Mist' ; } else if ( window . web3 . currentProvider . constructor . name === 'o' ) { return 'Parity' ; } return 'Local Interface' ; } param { code ? returns { void } private handleEstimateGasError ( e ) { if ( e ?. code && e . code . indexOf ( 'UNPREDICTABLE_GAS_LIMIT' ) > - 1 ) { this . toast . error ( 'Unable to predict gas limit. Falling back to high limit - excess gas will be returned.' ) ; } else if ( e ?. code && e . code . indexOf ( 'INSUFFICIENT_FUNDS' ) > - 1 ) { this . toast . error ( 'Unable to predict gas limit as you do not have sufficient Ether.' ) ; } else { console . error ( e ) ; } } static _ ( transactionOverlay , platformId , configs , web3modalService , toast ) { return new Web3WalletService ( transactionOverlay , platformId , configs , web3modalService , toast ) ; } }