diff --git a/packages/cli/src/actions.ts b/packages/cli/src/actions.ts index cb9024aa1..b5729e5c3 100644 --- a/packages/cli/src/actions.ts +++ b/packages/cli/src/actions.ts @@ -21,6 +21,7 @@ import { extractBooleanString, getSigConfirmation, getSize, + expirationRetry, } from './helpers'; import { logPk, @@ -110,13 +111,17 @@ export const mintTo = async (opts: Opts) => { export const initializeProtocol = async (opts: Opts) => { const cvg = await createCvg(opts); try { - const { response, protocol } = await cvg.protocol().initialize({ - collateralMint: new PublicKey(opts.collateralMint), - protocolTakerFee: opts.protocolTakerFee, - protocolMakerFee: opts.protocolMakerFee, - settlementTakerFee: opts.settlementTakerFee, - settlementMakerFee: opts.settlementMakerFee, - }); + const { response, protocol } = await expirationRetry( + () => + cvg.protocol().initialize({ + collateralMint: new PublicKey(opts.collateralMint), + protocolTakerFee: opts.protocolTakerFee, + protocolMakerFee: opts.protocolMakerFee, + settlementTakerFee: opts.settlementTakerFee, + settlementMakerFee: opts.settlementMakerFee, + }), + opts + ); logPk(protocol.address); logResponse(response); } catch (e) { @@ -127,16 +132,21 @@ export const initializeProtocol = async (opts: Opts) => { export const addInstrument = async (opts: Opts) => { const cvg = await createCvg(opts); try { - const { response } = await cvg.protocol().addInstrument({ - authority: cvg.rpc().getDefaultFeePayer(), - instrumentProgram: new PublicKey(opts.instrumentProgram), - canBeUsedAsQuote: extractBooleanString(opts, 'canBeUsedAsQuote'), - validateDataAccountAmount: opts.validateDataAccountAmount, - prepareToSettleAccountAmount: opts.prepareToSettleAccountAmount, - settleAccountAmount: opts.settleAccountAmount, - revertPreparationAccountAmount: opts.revertPreparationAccountAmount, - cleanUpAccountAmount: opts.cleanUpAccountAmount, - }); + const { response } = await expirationRetry( + () => + cvg.protocol().addInstrument({ + authority: cvg.rpc().getDefaultFeePayer(), + instrumentProgram: new PublicKey(opts.instrumentProgram), + canBeUsedAsQuote: extractBooleanString(opts, 'canBeUsedAsQuote'), + validateDataAccountAmount: opts.validateDataAccountAmount, + prepareToSettleAccountAmount: opts.prepareToSettleAccountAmount, + settleAccountAmount: opts.settleAccountAmount, + revertPreparationAccountAmount: opts.revertPreparationAccountAmount, + cleanUpAccountAmount: opts.cleanUpAccountAmount, + }), + opts + ); + logResponse(response); } catch (e) { logError(e); @@ -146,11 +156,20 @@ export const addInstrument = async (opts: Opts) => { export const addPrintTradeProvider = async (opts: Opts) => { const cvg = await createCvg(opts); try { - const { response } = await cvg.protocol().addPrintTradeProvider({ - printTradeProviderProgram: new PublicKey(opts.printTradeProviderProgram), - settlementCanExpire: extractBooleanString(opts, 'settlementCanExpire'), - validateResponseAccountAmount: opts.validateResponseAccountAmount, - }); + const { response } = await expirationRetry( + () => + cvg.protocol().addPrintTradeProvider({ + printTradeProviderProgram: new PublicKey( + opts.printTradeProviderProgram + ), + settlementCanExpire: extractBooleanString( + opts, + 'settlementCanExpire' + ), + validateResponseAccountAmount: opts.validateResponseAccountAmount, + }), + opts + ); logResponse(response); } catch (e) { logError(e); @@ -160,7 +179,10 @@ export const addPrintTradeProvider = async (opts: Opts) => { export const addBaseAsset = async (opts: Opts) => { const cvg = await createCvg(opts); try { - const baseAssets = await cvg.protocol().getBaseAssets(); + const baseAssets = await expirationRetry( + () => cvg.protocol().getBaseAssets(), + opts + ); const { oracleSource } = opts; let priceOracle: PriceOracle; @@ -312,7 +334,10 @@ export const registerMint = async (opts: Opts) => { }; const cvg = await createCvg(opts); try { - const { response } = await cvg.protocol().registerMint(getMintArgs()); + const { response } = await expirationRetry( + () => cvg.protocol().registerMint(getMintArgs()), + opts + ); logResponse(response); } catch (e) { logError(e); @@ -477,17 +502,21 @@ export const getCollateral = async (opts: Opts) => { export const initializeRiskEngine = async (opts: Opts) => { const cvg = await createCvg(opts); try { - const { response } = await cvg.riskEngine().initializeConfig({ - collateralMintDecimals: opts.collateralMintDecimals, - minCollateralRequirement: opts.minCollateralRequirement, - collateralForFixedQuoteAmountRfqCreation: - opts.collateralForFixedQuoteAmountRfqCreation, - safetyPriceShiftFactor: opts.safetyPriceShiftFactor, - overallSafetyFactor: opts.overallSafetyFace, - acceptedOracleStaleness: opts.acceptedOracleStaleness, - acceptedOracleConfidenceIntervalPortion: - opts.acceptedOracleConfidenceIntervalPortion, - }); + const { response } = await expirationRetry( + () => + cvg.riskEngine().initializeConfig({ + collateralMintDecimals: opts.collateralMintDecimals, + minCollateralRequirement: opts.minCollateralRequirement, + collateralForFixedQuoteAmountRfqCreation: + opts.collateralForFixedQuoteAmountRfqCreation, + safetyPriceShiftFactor: opts.safetyPriceShiftFactor, + overallSafetyFactor: opts.overallSafetyFace, + acceptedOracleStaleness: opts.acceptedOracleStaleness, + acceptedOracleConfidenceIntervalPortion: + opts.acceptedOracleConfidenceIntervalPortion, + }), + opts + ); logResponse(response); } catch (e) { logError(e); @@ -497,17 +526,21 @@ export const initializeRiskEngine = async (opts: Opts) => { export const updateRiskEngine = async (opts: Opts) => { const cvg = await createCvg(opts); try { - const { response } = await cvg.riskEngine().updateConfig({ - collateralMintDecimals: opts.collateralMintDecimals, - minCollateralRequirement: opts.minCollateralRequirement, - collateralForFixedQuoteAmountRfqCreation: - opts.collateralForFixedQuoteAmountRfqCreation, - safetyPriceShiftFactor: opts.safetyPriceShiftFactor, - overallSafetyFactor: opts.overallSafetyFace, - acceptedOracleStaleness: opts.acceptedOracleStaleness, - acceptedOracleConfidenceIntervalPortion: - opts.acceptedOracleConfidenceIntervalPortion, - }); + const { response } = await expirationRetry( + () => + cvg.riskEngine().updateConfig({ + collateralMintDecimals: opts.collateralMintDecimals, + minCollateralRequirement: opts.minCollateralRequirement, + collateralForFixedQuoteAmountRfqCreation: + opts.collateralForFixedQuoteAmountRfqCreation, + safetyPriceShiftFactor: opts.safetyPriceShiftFactor, + overallSafetyFactor: opts.overallSafetyFace, + acceptedOracleStaleness: opts.acceptedOracleStaleness, + acceptedOracleConfidenceIntervalPortion: + opts.acceptedOracleConfidenceIntervalPortion, + }), + opts + ); logResponse(response); } catch (e) { logError(e); @@ -536,10 +569,14 @@ export const getRiskEngineConfig = async (opts: Opts) => { export const setRiskEngineInstrumentType = async (opts: Opts) => { const cvg = await createCvg(opts); try { - const { response } = await cvg.riskEngine().setInstrumentType({ - instrumentProgram: new PublicKey(opts.program), - instrumentType: opts.type, - }); + const { response } = await expirationRetry( + () => + cvg.riskEngine().setInstrumentType({ + instrumentProgram: new PublicKey(opts.program), + instrumentType: opts.type, + }), + opts + ); logResponse(response); } catch (e) { logError(e); @@ -550,21 +587,25 @@ export const setRiskEngineCategoriesInfo = async (opts: Opts) => { const newValue = opts.newValue.split(',').map((x: string) => parseFloat(x)); const cvg = await createCvg(opts); try { - const { response } = await cvg.riskEngine().setRiskCategoriesInfo({ - changes: [ - { - value: toRiskCategoryInfo(newValue[0], newValue[1], [ - toScenario(newValue[2], newValue[3]), - toScenario(newValue[4], newValue[5]), - toScenario(newValue[6], newValue[7]), - toScenario(newValue[8], newValue[9]), - toScenario(newValue[10], newValue[11]), - toScenario(newValue[12], newValue[13]), - ]), - category: opts.category, - }, - ], - }); + const { response } = await expirationRetry( + () => + cvg.riskEngine().setRiskCategoriesInfo({ + changes: [ + { + value: toRiskCategoryInfo(newValue[0], newValue[1], [ + toScenario(newValue[2], newValue[3]), + toScenario(newValue[4], newValue[5]), + toScenario(newValue[6], newValue[7]), + toScenario(newValue[8], newValue[9]), + toScenario(newValue[10], newValue[11]), + toScenario(newValue[12], newValue[13]), + ]), + category: opts.category, + }, + ], + }), + opts + ); logResponse(response); } catch (e) { logError(e); diff --git a/packages/cli/src/cvg.ts b/packages/cli/src/cvg.ts index 91e4d1a6e..e570dfd85 100644 --- a/packages/cli/src/cvg.ts +++ b/packages/cli/src/cvg.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import { Connection, Keypair } from '@solana/web3.js'; import { Convergence, keypairIdentity } from '@convergence-rfq/sdk'; -import { resolveMaxRetriesArg, resolveTxPriorityArg } from './helpers'; +import { resolveTxPriorityArg } from './helpers'; export type Opts = any; @@ -9,8 +9,6 @@ export const createCvg = async (opts: Opts): Promise => { const buffer = JSON.parse(readFileSync(opts.keypairFile, 'utf8')); const user = Keypair.fromSecretKey(new Uint8Array(buffer)); const txPriorityString: string = opts.txPriorityFee; - const maxRetriesString = opts.maxRetries; - const maxRetries = resolveMaxRetriesArg(maxRetriesString); const txPriority = resolveTxPriorityArg(txPriorityString); const cvg = new Convergence( @@ -20,7 +18,6 @@ export const createCvg = async (opts: Opts): Promise => { { skipPreflight: opts.skipPreflight, transactionPriority: txPriority, - maxRetries, } ); cvg.use(keypairIdentity(user)); diff --git a/packages/cli/src/groups/hxro.ts b/packages/cli/src/groups/hxro.ts index f1a505766..d2e6ec2c6 100644 --- a/packages/cli/src/groups/hxro.ts +++ b/packages/cli/src/groups/hxro.ts @@ -4,7 +4,7 @@ import { Command } from 'commander'; import { PublicKey } from '@solana/web3.js'; import { HxroProductInfo } from '@convergence-rfq/sdk'; -import { addCmd } from '../helpers'; +import { addCmd, expirationRetry } from '../helpers'; import { createCvg, Opts } from '../cvg'; import { logError, logHxroConfig, logResponse } from '../logger'; @@ -19,9 +19,11 @@ const initializeConfigCmd = (c: Command) => const initializeConfig = async (opts: Opts) => { const cvg = await createCvg(opts); try { - const response = await cvg - .hxro() - .initializeConfig({ validMpg: new PublicKey(opts.validMpg) }); + const response = await expirationRetry( + () => + cvg.hxro().initializeConfig({ validMpg: new PublicKey(opts.validMpg) }), + opts + ); logResponse(response); } catch (e) { logError(e); @@ -39,9 +41,10 @@ const modifyConfigCmd = (c: Command) => const modifyConfig = async (opts: Opts) => { const cvg = await createCvg(opts); try { - const response = await cvg - .hxro() - .modifyConfig({ validMpg: new PublicKey(opts.validMpg) }); + const response = await expirationRetry( + () => cvg.hxro().modifyConfig({ validMpg: new PublicKey(opts.validMpg) }), + opts + ); logResponse(response); } catch (e) { logError(e); @@ -101,9 +104,13 @@ const initializeOperatorTRG = async (opts: Opts) => { opts.hxroRiskEngine !== '' ? new PublicKey(opts.hxroRiskEngine) : undefined; - const response = await cvg.hxro().initializeOperatorTraderRiskGroup({ - hxroRiskEngineAddress, - }); + const response = await expirationRetry( + () => + cvg.hxro().initializeOperatorTraderRiskGroup({ + hxroRiskEngineAddress, + }), + opts + ); logResponse(response); } catch (e) { logError(e); diff --git a/packages/cli/src/groups/spotInstrument.ts b/packages/cli/src/groups/spotInstrument.ts index 9f63a0dd6..c47d76b44 100644 --- a/packages/cli/src/groups/spotInstrument.ts +++ b/packages/cli/src/groups/spotInstrument.ts @@ -2,7 +2,7 @@ import { Command } from 'commander'; -import { addCmd } from '../helpers'; +import { addCmd, expirationRetry } from '../helpers'; import { createCvg, Opts } from '../cvg'; import { logError, logResponse, logSpotInstrumentConfig } from '../logger'; @@ -23,9 +23,10 @@ const initializeConfigCmd = (c: Command) => const initializeConfig = async (opts: Opts) => { const cvg = await createCvg(opts); try { - const response = await cvg - .spotInstrument() - .initializeConfig({ feeBps: opts.feeBps }); + const response = await expirationRetry( + () => cvg.spotInstrument().initializeConfig({ feeBps: opts.feeBps }), + opts + ); logResponse(response); } catch (e) { logError(e); diff --git a/packages/cli/src/helpers.ts b/packages/cli/src/helpers.ts index c0da41811..c2f939bb6 100644 --- a/packages/cli/src/helpers.ts +++ b/packages/cli/src/helpers.ts @@ -188,3 +188,31 @@ export const resolveMaxRetriesArg = (maxRetries: string): number => { } return maxRetriesInNumber; }; + +export async function expirationRetry( + fn: () => Promise, + opts: Opts +): Promise { + let { maxRetries } = opts; + maxRetries = resolveMaxRetriesArg(maxRetries); + if (maxRetries === 0) return await fn(); + let retryCount = 0; + + while (retryCount < maxRetries) { + try { + return await fn(); + } catch (error) { + if (!isTransactionExpiredBlockheightExceededError(error)) throw error; + retryCount++; + console.error(`Attempt ${retryCount + 1} tx expired. Retrying...`); + } + } + throw new Error('Max Tx Expiration retries exceeded'); +} + +function isTransactionExpiredBlockheightExceededError(error: unknown) { + return ( + error instanceof Error && + error.message.includes('TransactionExpiredBlockheightExceededError') + ); +} diff --git a/packages/js/src/Convergence.ts b/packages/js/src/Convergence.ts index d34dc12a4..3108eff55 100644 --- a/packages/js/src/Convergence.ts +++ b/packages/js/src/Convergence.ts @@ -11,7 +11,6 @@ export type ConvergenceOptions = { cluster?: Cluster; skipPreflight?: boolean; transactionPriority?: TransactionPriority; - maxRetries?: number; }; export class Convergence { @@ -19,14 +18,12 @@ export class Convergence { public readonly cluster: Cluster; public readonly skipPreflight: boolean; public readonly transactionPriority: TransactionPriority; - public readonly maxRetries: number; constructor(connection: Connection, options: ConvergenceOptions = {}) { this.connection = connection; this.cluster = options.cluster ?? resolveClusterFromConnection(connection); this.skipPreflight = options.skipPreflight ?? false; this.transactionPriority = options.transactionPriority ?? 'none'; - this.maxRetries = options.maxRetries ?? 0; this.use(corePlugins()); } diff --git a/packages/js/src/plugins/rpcModule/RpcClient.ts b/packages/js/src/plugins/rpcModule/RpcClient.ts index 54064adfc..3cd8d955e 100644 --- a/packages/js/src/plugins/rpcModule/RpcClient.ts +++ b/packages/js/src/plugins/rpcModule/RpcClient.ts @@ -233,13 +233,6 @@ export class RpcClient { const rawTransaction = transaction.serialize(); - const { maxRetries } = this.convergence; - if (maxRetries > 0) { - confirmOptions = { - ...confirmOptions, - maxRetries, - }; - } const signature = await this.sendRawTransaction( rawTransaction, confirmOptions, diff --git a/packages/js/src/utils/TransactionBuilder.ts b/packages/js/src/utils/TransactionBuilder.ts index cda667efa..3fa577864 100644 --- a/packages/js/src/utils/TransactionBuilder.ts +++ b/packages/js/src/utils/TransactionBuilder.ts @@ -267,14 +267,6 @@ export class TransactionBuilder { convergence: Convergence, confirmOptions?: ConfirmOptions ): Promise<{ response: SendAndConfirmTransactionResponse } & C> { - const { maxRetries } = convergence; - - if (maxRetries > 0) { - confirmOptions = { - ...confirmOptions, - maxRetries, - }; - } const response = await convergence .rpc() .sendAndConfirmTransaction(this, [], confirmOptions);