import { Injectable } from '@angular/core' ; import Web3 from 'web3' ; import BigNumber from 'bignumber.js' ; import { TransactionReceipt } from 'web3-eth' ; import { TransactionOptions } from '@shared/models/blockchain/transaction-options' ; import { AbiItem } from 'web3-utils' ; import CustomError from '@core/errors/models/custom-error' ; import ERC20_TOKEN_ABI from '@core/services/blockchain/constants/erc-20-abi' ; import { WalletConnectorService } from '@core/services/blockchain/wallets/wallet-connector-service/wallet-connector.service' ; import { GasApiService } from '@core/services/backend/gas-api/gas-api.service' ; import FailedToCheckForTransactionReceiptError from '@core/errors/models/common/failed-to-check-for-transaction-receipt-error' ; import TransactionRevertedError from '@core/errors/models/common/transaction-reverted-error' ; import { LowGasError } from '@core/errors/models/provider/low-gas-error' ; import { UserRejectError } from '@core/errors/models/provider/user-reject-error' ; type Web3Error = { message : string ; code : number ; } ; @ Injectable ( { providedIn : 'root' } ) export class EthLikeWeb3PrivateService { private defaultMockGas : string ; private readonly web3 : Web3 ; private get address ( ) : string { return this . walletConnectorService . address ; } constructor ( private readonly walletConnectorService , private readonly gasApiService ) { this . web3 = walletConnectorService . web3 ; } private static parseError ( err ) { if ( err . message . includes ( 'Transaction has been reverted by the EVM' ) ) { return new TransactionRevertedError ( ) ; } if ( err . message . includes ( 'Failed to check for transaction receipt' ) ) { return new FailedToCheckForTransactionReceiptError ( ) ; } if ( err . code === - 32603 ) { return new LowGasError ( ) ; } if ( err . code === 4001 ) { return new UserRejectError ( ) ; } try { const errorMessage = JSON . parse ( err . message . slice ( 24 ) ) . message ; if ( errorMessage ) { return new CustomError ( errorMessage ) ; } } catch ( _ignored ) { } return err as unknown as Error ; } param gasPrice private async calculateGasPrice ( gasPrice ? ) { const blockchain = this . walletConnectorService . networkName ; const minGasPrice = await this . gasApiService . getMinGasPriceInBlockchain ( blockchain ) . toPromise ( ) ; if ( ! minGasPrice ?. isFinite ( ) || minGasPrice . eq ( 0 ) ) { return gasPrice ; } if ( ! gasPrice ) { return minGasPrice . toFixed ( ) ; } return BigNumber . max ( gasPrice , minGasPrice ) . toFixed ( ) ; } param contractAddress param toAddress param amount param options param options . onTransactionHash return public async transferTokens ( contractAddress , toAddress , amount , options = { } ) { const contract = new this . web3 . eth . Contract ( ERC20_TOKEN_ABI , contractAddress ) ; const gasPrice = await this . calculateGasPrice ( options . gasPrice ) ; return new Promise ( ( resolve , reject ) => { this . emitTransaction ( ) ; contract . methods . transfer ( toAddress , amount . toString ( ) ) . send ( { from : this . address , ... ( ( options . gas || this . defaultMockGas ) && { gas : options . gas || this . defaultMockGas } ) , ... ( gasPrice && { gasPrice } ) } ) . on ( 'transactionHash' , options . onTransactionHash || ( ( ) => { } ) ) . on ( 'receipt' , resolve ) . on ( 'error' , ( err ) => { console . error ( `Tokens transfer error. ${ err }` ) ; reject ( EthLikeWeb3PrivateService . parseError ( err ) ) ; } ) ; } ) ; } param contractAddress param toAddress param amount return public async transferTokensWithOnHashResolve ( contractAddress , toAddress , amount ) { const contract = new this . web3 . eth . Contract ( ERC20_TOKEN_ABI , contractAddress ) ; return new Promise ( ( resolve , reject ) => { this . emitTransaction ( ) ; contract . methods . transfer ( toAddress , amount . toString ( ) ) . send ( { from : this . address , ... ( this . defaultMockGas && { gas : this . defaultMockGas } ) } ) . on ( 'transactionHash' , ( hash ) => resolve ( hash ) ) . on ( 'error' , ( err ) => { console . error ( `Tokens transfer error. ${ err }` ) ; reject ( EthLikeWeb3PrivateService . parseError ( err ) ) ; } ) ; } ) ; } param toAddress param value param options param options . onTransactionHash param options . inWei param options . data param options . gas param options . gasPrice return public async trySendTransaction ( toAddress , value , options = { } ) { try { await this . web3 . eth . call ( { from : this . address , to : toAddress , value : options . inWei ? value . toString ( ) : this . ethToWei ( value ) , ... ( ( options . gas || this . defaultMockGas ) && { gas : options . gas || this . defaultMockGas } ) , ... ( options . data && { data : options . data } ) } ) ; return this . sendTransaction ( toAddress , value , options ) ; } catch ( err ) { console . error ( 'Send transaction error' , err ) ; throw EthLikeWeb3PrivateService . parseError ( err ) ; } } param toAddress param value param options param options . onTransactionHash param options . inWei param options . data param options . gas param options . gasPrice return public async sendTransaction ( toAddress , value , options = { } ) { const gasPrice = await this . calculateGasPrice ( options . gasPrice ) ; return new Promise ( ( resolve , reject ) => { this . emitTransaction ( ) ; this . web3 . eth . sendTransaction ( { from : this . address , to : toAddress , value : options . inWei ? value . toString ( ) : this . ethToWei ( value ) , ... ( ( options . gas || this . defaultMockGas ) && { gas : options . gas || this . defaultMockGas } ) , ... ( options . data && { data : options . data } ) , ... ( gasPrice && { gasPrice } ) } ) . on ( 'transactionHash' , options . onTransactionHash || ( ( ) => { } ) ) . on ( 'receipt' , receipt => resolve ( receipt ) ) . on ( 'error' , err => { console . error ( 'Send transaction error' , err ) ; reject ( EthLikeWeb3PrivateService . parseError ( err as unknown as Web3Error ) ) ; } ) ; } ) ; } param toAddress param value param options param options . inWei return public async sendTransactionWithOnHashResolve ( toAddress , value , options = { } ) { const gasPrice = await this . calculateGasPrice ( options . gasPrice ) ; return new Promise ( ( resolve , reject ) => { this . emitTransaction ( ) ; this . web3 . eth . sendTransaction ( { from : this . address , to : toAddress , value : options . inWei ? value . toString ( ) : this . ethToWei ( value ) , ... ( gasPrice && { gasPrice } ) } ) . on ( 'transactionHash' , hash => resolve ( hash ) ) . on ( 'error' , err => { console . error ( `Tokens transfer error. ${ err }` ) ; reject ( EthLikeWeb3PrivateService . parseError ( err as unknown as Web3Error ) ) ; } ) ; } ) ; } param tokenAddress param spenderAddress param value param options param options . onTransactionHash return public async approveTokens ( tokenAddress , spenderAddress , value , options = { } ) { let rawValue ; if ( value === 'infinity' ) { rawValue = new BigNumber ( 2 ) . pow ( 256 ) . minus ( 1 ) ; } else { rawValue = value ; } const contract = new this . web3 . eth . Contract ( ERC20_TOKEN_ABI , tokenAddress ) ; const gasPrice = await this . calculateGasPrice ( options . gasPrice ) ; return new Promise ( ( resolve , reject ) => { this . emitTransaction ( ) ; contract . methods . approve ( spenderAddress , rawValue . toFixed ( 0 ) ) . send ( { from : this . address , ... ( ( options . gas || this . defaultMockGas ) && { gas : options . gas || this . defaultMockGas } ) , ... ( gasPrice && { gasPrice } ) } ) . on ( 'transactionHash' , options . onTransactionHash || ( ( ) => { } ) ) . on ( 'receipt' , resolve ) . on ( 'error' , ( err ) => { console . error ( `Tokens approve error. ${ err }` ) ; reject ( EthLikeWeb3PrivateService . parseError ( err ) ) ; } ) ; } ) ; } param contractAddress param contractAbi param methodName param methodArguments param options param options . value param options . gas param allowError public async tryExecuteContractMethod ( contractAddress , contractAbi , methodName , methodArguments , options = { } , allowError ? ) { const contract = new this . web3 . eth . Contract ( contractAbi , contractAddress ) ; try { await contract . methods [ methodName ] ( ... methodArguments ) . call ( { from : this . address , ... ( options . value && { value : options . value } ) , ... ( ( options . gas || this . defaultMockGas ) && { gas : options . gas || this . defaultMockGas } ) } ) ; return this . executeContractMethod ( contractAddress , contractAbi , methodName , methodArguments , options ) ; } catch ( err ) { if ( allowError ?. ( err ) ) { return this . executeContractMethod ( contractAddress , contractAbi , methodName , methodArguments , options ) ; } console . error ( 'Method execution error:' , err ) ; throw EthLikeWeb3PrivateService . parseError ( err ) ; } } param contractAddress param contractAbi param methodName param methodArguments param options param options . onTransactionHash param options . value return public async executeContractMethod ( contractAddress , contractAbi , methodName , methodArguments , options = { } ) { const contract = new this . web3 . eth . Contract ( contractAbi , contractAddress ) ; const gasPrice = await this . calculateGasPrice ( options . gasPrice ) ; return new Promise ( ( resolve , reject ) => { this . emitTransaction ( ) ; contract . methods [ methodName ] ( ... methodArguments ) . send ( { from : this . address , ... ( options . value && { value : options . value } ) , ... ( ( options . gas || this . defaultMockGas ) && { gas : options . gas || this . defaultMockGas } ) , ... ( gasPrice && { gasPrice } ) } ) . on ( 'transactionHash' , options . onTransactionHash || ( ( ) => { } ) ) . on ( 'receipt' , resolve ) . on ( 'error' , ( err ) => { console . error ( `Method execution error:` , err ) ; reject ( EthLikeWeb3PrivateService . parseError ( err ) ) ; } ) ; } ) ; } param contractAddress param contractAbi param methodName param methodArguments return public executeContractMethodWithOnHashResolve ( contractAddress , contractAbi , methodName , methodArguments ) { const contract = new this . web3 . eth . Contract ( contractAbi , contractAddress ) ; return new Promise ( ( resolve , reject ) => { this . emitTransaction ( ) ; contract . methods [ methodName ] ( ... methodArguments ) . send ( { from : this . address , ... ( this . defaultMockGas && { gas : this . defaultMockGas } ) } ) . on ( 'transactionHash' , resolve ) . on ( 'error' , ( err ) => { console . error ( `Tokens approve error. ${ err }` ) ; reject ( EthLikeWeb3PrivateService . parseError ( err ) ) ; } ) ; } ) ; } param tokenAddress param spenderAddress public async unApprove ( tokenAddress , spenderAddress ) { return this . approveTokens ( tokenAddress , spenderAddress , new BigNumber ( 0 ) ) ; } param value private ethToWei ( value ) { return this . web3 . utils . toWei ( value . toString ( ) , 'ether' ) ; } param value private weiToEth ( value ) { return this . web3 . utils . fromWei ( value . toString ( ) , 'ether' ) ; } /**
     * Emits transaction action to wallet.
     */ private emitTransaction ( ) { setTimeout ( ( ) => { this . walletConnectorService . emitTransaction ( ) ; } , 500 ) ; } }