import { delay , inject , singleton } from "tsyringe" ; import { TYPES } from "../types" ; import Axios from "axios" ; import { ChainsService } from "../chains" ; import { PermissionService } from "../permission" ; import { TendermintTxTracer } from "@keplr-wallet/cosmos/build/tx-tracer" ; import { Notification } from "./types" ; import { Buffer } from "buffer/" ; interface CosmosSdkError { codespace : string ; code : number ; message : string ; } interface ABCIMessageLog { msg_index : number ; success : boolean ; log : string ; } @ singleton ( ) export class BackgroundTxService { constructor ( @ inject ( delay ( ( ) => ChainsService ) ) protected readonly chainsService , @ inject ( delay ( ( ) => PermissionService ) ) public readonly permissionService , @ inject ( TYPES . Notification ) protected readonly notification ) { } async sendTx ( chainId , tx , mode ) { const chainInfo = await this . chainsService . getChainInfo ( chainId ) ; const restInstance = Axios . create ( { ... { baseURL : chainInfo . rest , } , ... chainInfo . restConfig , } ) ; this . notification . create ( { iconRelativeUrl : "assets/temp-icon.svg" , title : "Tx is pending..." , message : "Wait a second" , } ) ; const isProtoTx = Buffer . isBuffer ( tx ) || tx instanceof Uint8Array ; const params = isProtoTx ? { tx_bytes : Buffer . from ( tx as any ) . toString ( "base64" ) , mode : ( ( ) => { switch ( mode ) { case "async" : return "BROADCAST_MODE_ASYNC" ; case "block" : return "BROADCAST_MODE_BLOCK" ; case "sync" : return "BROADCAST_MODE_SYNC" ; default : return "BROADCAST_MODE_UNSPECIFIED" ; } } ) ( ) , } : { tx , mode : mode , } ; try { const result = await restInstance . post ( isProtoTx ? "/cosmos/tx/v1beta1/txs" : "/txs" , params ) ; const txResponse = isProtoTx ? result . data [ "tx_response" ] : result . data ; if ( txResponse . code != null && txResponse . code !== 0 ) { throw new Error ( txResponse [ "raw_log" ] ) ; } const txHash = Buffer . from ( txResponse . txhash , "hex" ) ; const txTracer = new TendermintTxTracer ( chainInfo . rpc , "/websocket" ) ; txTracer . traceTx ( txHash ) . then ( ( tx ) => { txTracer . close ( ) ; BackgroundTxService . processTxResultNotification ( this . notification , tx ) ; } ) ; return txHash ; } catch ( e ) { console . log ( e ) ; BackgroundTxService . processTxErrorNotification ( this . notification , e ) ; throw e ; } } private static processTxResultNotification ( notification , result ) { try { if ( result . mode === "commit" ) { if ( result . checkTx . code !== undefined && result . checkTx . code !== 0 ) { throw new Error ( result . checkTx . log ) ; } if ( result . deliverTx . code !== undefined && result . deliverTx . code !== 0 ) { throw new Error ( result . deliverTx . log ) ; } } else { if ( result . code != null && result . code !== 0 ) { const log = result . log ?? ( result as any ) [ "raw_log" ] ; throw new Error ( log ) ; } } notification . create ( { iconRelativeUrl : "assets/temp-icon.svg" , title : "Tx succeeds" , message : "Congratulations!" , } ) ; } catch ( e ) { BackgroundTxService . processTxErrorNotification ( notification , e ) ; } } private static processTxErrorNotification ( notification , e ) { console . log ( e ) ; let message = e . message ; const regResult = /code:\s*(-?\d+),\s*message:\s*(.+),\sdata:\s(.+)/g . exec ( e . message ) ; if ( regResult && regResult . length === 4 ) { message = regResult [ 3 ] ; } try { const sdkErr = JSON . parse ( e . message ) ; if ( sdkErr ?. message ) { message = sdkErr . message ; } } catch { } try { const abciMessageLogs = JSON . parse ( e . message ) ; if ( abciMessageLogs && abciMessageLogs . length > 0 ) { for ( const abciMessageLog of abciMessageLogs ) { if ( ! abciMessageLog . success ) { const sdkErr = JSON . parse ( abciMessageLog . log ) ; if ( sdkErr ?. message ) { message = sdkErr . message ; break ; } } } } } catch { } notification . create ( { iconRelativeUrl : "assets/temp-icon.svg" , title : "Tx failed" , message , } ) ; } }