@@ -29,10 +29,14 @@ import {
2929 getStdFee ,
3030 BigNumberInBase ,
3131 DEFAULT_BLOCK_TIMEOUT_HEIGHT ,
32+ sleep ,
3233} from '@injectivelabs/utils'
3334import {
34- GeneralException ,
35+ ThrownException ,
3536 isThrownException ,
37+ GeneralException ,
38+ ChainCosmosErrorCode ,
39+ TransactionChainErrorModule ,
3640 TransactionException ,
3741 UnspecifiedErrorCode ,
3842} from '@injectivelabs/exceptions'
@@ -56,7 +60,10 @@ import {
5660 isEip712V2OnlyWallet ,
5761} from '../strategies/wallet-strategy/utils.js'
5862import { Wallet , WalletDeviceType } from '../types/index.js'
59- import { createEip712StdSignDoc , KeplrWallet } from '../utils/wallets/keplr/index.js'
63+ import {
64+ createEip712StdSignDoc ,
65+ KeplrWallet ,
66+ } from '../utils/wallets/keplr/index.js'
6067import { isCosmosAminoOnlyWallet } from '../utils/index.js'
6168import { LeapWallet } from '../utils/wallets/index.js'
6269import { checkIfTxRunOutOfGas } from './helper.js'
@@ -77,6 +84,15 @@ const getEthereumWalletPubKey = <T>({
7784 return hexToBase64 ( recoverTypedSignaturePubKey ( eip712TypedData , signature ) )
7885}
7986
87+ const defaultRetriesConfig = ( ) => ( {
88+ [ `${ TransactionChainErrorModule . CosmosSdk } -${ ChainCosmosErrorCode . ErrMempoolIsFull } ` ] :
89+ {
90+ retries : 0 ,
91+ maxRetries : 10 ,
92+ timeout : 1000 ,
93+ } ,
94+ } )
95+
8096/**
8197 * This class is used to broadcast transactions
8298 * using the WalletStrategy as a handler
@@ -101,6 +117,8 @@ export class MsgBroadcaster {
101117
102118 public gasBufferCoefficient : number = 1.2
103119
120+ public retriesOnError = defaultRetriesConfig ( )
121+
104122 constructor ( options : MsgBroadcasterOptions ) {
105123 const networkInfo = getNetworkInfo ( options . network )
106124
@@ -154,10 +172,10 @@ export class MsgBroadcaster {
154172
155173 try {
156174 return isCosmosWallet ( walletStrategy . wallet )
157- ? this . broadcastCosmos ( txWithAddresses )
175+ ? await this . broadcastCosmos ( txWithAddresses )
158176 : isEip712V2OnlyWallet ( walletStrategy . wallet )
159- ? this . broadcastWeb3V2 ( txWithAddresses )
160- : this . broadcastWeb3 ( txWithAddresses )
177+ ? await this . broadcastWeb3V2 ( txWithAddresses )
178+ : await this . broadcastWeb3 ( txWithAddresses )
161179 } catch ( e ) {
162180 const error = e as any
163181
@@ -198,8 +216,8 @@ export class MsgBroadcaster {
198216
199217 try {
200218 return isCosmosWallet ( walletStrategy . wallet )
201- ? this . broadcastCosmos ( txWithAddresses )
202- : this . broadcastWeb3V2 ( txWithAddresses )
219+ ? await this . broadcastCosmos ( txWithAddresses )
220+ : await this . broadcastWeb3V2 ( txWithAddresses )
203221 } catch ( e ) {
204222 const error = e as any
205223
@@ -241,18 +259,20 @@ export class MsgBroadcaster {
241259 }
242260
243261 return isCosmosWallet ( walletStrategy . wallet )
244- ? this . broadcastCosmos ( txWithAddresses )
245- : this . broadcastWeb3WithFeeDelegation ( txWithAddresses )
262+ ? await this . broadcastCosmos ( txWithAddresses )
263+ : await this . broadcastWeb3WithFeeDelegation ( txWithAddresses )
246264 }
247265
248266 /**
249267 * Broadcasting the transaction using the feeDelegation
250268 * support approach for both cosmos and ethereum native wallets
251269 *
252270 * @param tx
253- * @returns {string } transaction hash
271+ * @returns {TxResponse }
254272 */
255- async broadcastWithFeeDelegation ( tx : MsgBroadcasterTxOptions ) {
273+ async broadcastWithFeeDelegation (
274+ tx : MsgBroadcasterTxOptions ,
275+ ) : Promise < TxResponse > {
256276 const { options } = this
257277 const { walletStrategy } = options
258278 const txWithAddresses = {
@@ -273,8 +293,8 @@ export class MsgBroadcaster {
273293
274294 try {
275295 return isCosmosWallet ( walletStrategy . wallet )
276- ? this . broadcastCosmosWithFeeDelegation ( txWithAddresses )
277- : this . broadcastWeb3WithFeeDelegation ( txWithAddresses )
296+ ? await this . broadcastCosmosWithFeeDelegation ( txWithAddresses )
297+ : await this . broadcastWeb3WithFeeDelegation ( txWithAddresses )
278298 } catch ( e ) {
279299 const error = e as any
280300
@@ -552,7 +572,7 @@ export class MsgBroadcaster {
552572 . toNumber ( )
553573 }
554574
555- const txResponse = await transactionApi . prepareTxRequest ( {
575+ const prepareTxResponse = await transactionApi . prepareTxRequest ( {
556576 timeoutHeight,
557577 memo : tx . memo ,
558578 message : web3Msgs ,
@@ -563,45 +583,52 @@ export class MsgBroadcaster {
563583 } )
564584
565585 const signature = await walletStrategy . signEip712TypedData (
566- txResponse . data ,
586+ prepareTxResponse . data ,
567587 tx . ethereumAddress ,
568588 )
569589
570- const response = await transactionApi . broadcastTxRequest ( {
571- signature,
572- txResponse,
573- message : web3Msgs ,
574- chainId : ethereumChainId ,
575- } )
590+ const broadcast = async ( ) =>
591+ await transactionApi . broadcastTxRequest ( {
592+ signature,
593+ message : web3Msgs ,
594+ txResponse : prepareTxResponse ,
595+ chainId : ethereumChainId ,
596+ } )
576597
577598 try {
578- const txResponse = await new TxGrpcApi ( endpoints . grpc ) . fetchTxPoll (
579- response . txHash ,
580- )
599+ const response = await broadcast ( )
581600
582- return txResponse
601+ return await new TxGrpcApi ( endpoints . grpc ) . fetchTxPoll ( response . txHash )
583602 } catch ( e ) {
584- /**
585- * First MsgExec transaction with a PrivateKey wallet
586- * always runs out of gas for some reason, temporary solution
587- * to just broadcast the transaction twice
588- **/
589- if (
590- walletStrategy . wallet === Wallet . PrivateKey &&
591- checkIfTxRunOutOfGas ( e )
592- ) {
593- /** Account Details * */
594- const accountDetails = await new ChainGrpcAuthApi (
595- endpoints . grpc ,
596- ) . fetchAccount ( tx . injectiveAddress )
597- const { baseAccount } = accountDetails
598-
599- /** We only do it on the first account tx fail */
600- if ( baseAccount . sequence > 1 ) {
601- throw e
603+ const error = e as any
604+
605+ if ( isThrownException ( error ) ) {
606+ const exception = error as ThrownException
607+
608+ /**
609+ * First MsgExec transaction with a PrivateKey wallet
610+ * always runs out of gas for some reason, temporary solution
611+ * to just broadcast the transaction twice
612+ **/
613+ if (
614+ walletStrategy . wallet === Wallet . PrivateKey &&
615+ checkIfTxRunOutOfGas ( exception )
616+ ) {
617+ /** Account Details * */
618+ const accountDetails = await new ChainGrpcAuthApi (
619+ endpoints . grpc ,
620+ ) . fetchAccount ( tx . injectiveAddress )
621+ const { baseAccount } = accountDetails
622+
623+ /** We only do it on the first account tx fail */
624+ if ( baseAccount . sequence > 1 ) {
625+ throw e
626+ }
627+
628+ return await this . broadcastWeb3WithFeeDelegation ( tx )
602629 }
603630
604- return await this . broadcastWeb3WithFeeDelegation ( tx )
631+ return await this . retryOnException ( exception , broadcast )
605632 }
606633
607634 throw e
@@ -949,22 +976,38 @@ export class MsgBroadcaster {
949976 const transactionApi = new IndexerGrpcTransactionApi (
950977 endpoints . web3gw || endpoints . indexer ,
951978 )
952- const response = await transactionApi . broadcastCosmosTxRequest ( {
953- address : tx . injectiveAddress ,
954- txRaw : createTxRawFromSigResponse ( directSignResponse ) ,
955- signature : directSignResponse . signature . signature ,
956- pubKey : directSignResponse . signature . pub_key || {
957- value : pubKey ,
958- type : '/injective.crypto.v1beta1.ethsecp256k1.PubKey' ,
959- } ,
960- } )
961979
962- // Re-enable tx gas check removed above
963- if ( walletStrategy . wallet === Wallet . Keplr ) {
964- new KeplrWallet ( chainId ) . enableGasCheck ( )
965- }
980+ const broadcast = async ( ) =>
981+ await transactionApi . broadcastCosmosTxRequest ( {
982+ address : tx . injectiveAddress ,
983+ txRaw : createTxRawFromSigResponse ( directSignResponse ) ,
984+ signature : directSignResponse . signature . signature ,
985+ pubKey : directSignResponse . signature . pub_key || {
986+ value : pubKey ,
987+ type : '/injective.crypto.v1beta1.ethsecp256k1.PubKey' ,
988+ } ,
989+ } )
990+
991+ try {
992+ const response = await broadcast ( )
966993
967- return await new TxGrpcApi ( endpoints . grpc ) . fetchTxPoll ( response . txHash )
994+ // Re-enable tx gas check removed above
995+ if ( walletStrategy . wallet === Wallet . Keplr ) {
996+ new KeplrWallet ( chainId ) . enableGasCheck ( )
997+ }
998+
999+ return await new TxGrpcApi ( endpoints . grpc ) . fetchTxPoll ( response . txHash )
1000+ } catch ( e ) {
1001+ const error = e as any
1002+
1003+ if ( isThrownException ( error ) ) {
1004+ const exception = error as ThrownException
1005+
1006+ return await this . retryOnException ( exception , broadcast )
1007+ }
1008+
1009+ throw e
1010+ }
9681011 }
9691012
9701013 /**
@@ -1077,4 +1120,41 @@ export class MsgBroadcaster {
10771120
10781121 return simulationResponse
10791122 }
1123+
1124+ private async retryOnException < T > (
1125+ exception : ThrownException ,
1126+ retryLogic : ( ) => Promise < T > ,
1127+ ) : Promise < any > {
1128+ const errorsToRetry = Object . keys ( this . retriesOnError )
1129+ const errorKey =
1130+ `${ exception . contextModule } -${ exception . contextCode } ` as keyof typeof this . retriesOnError
1131+
1132+ if ( ! errorsToRetry . includes ( errorKey ) ) {
1133+ throw exception
1134+ }
1135+
1136+ const retryConfig = this . retriesOnError [ errorKey ]
1137+
1138+ if ( retryConfig . retries >= retryConfig . maxRetries ) {
1139+ this . retriesOnError = defaultRetriesConfig ( )
1140+
1141+ throw exception
1142+ }
1143+
1144+ await sleep ( retryConfig . timeout )
1145+
1146+ try {
1147+ retryConfig . retries += 1
1148+
1149+ return await retryLogic ( )
1150+ } catch ( e ) {
1151+ const error = e as any
1152+
1153+ if ( isThrownException ( error ) ) {
1154+ return this . retryOnException ( error , retryLogic )
1155+ }
1156+
1157+ throw e
1158+ }
1159+ }
10801160}
0 commit comments