import { ethers, PopulatedTransaction, providers } from 'ethers'; import { TransactionErrorHandler } from './error-handler/error-handler'; import { TransactionQueueHandler } from './transaction-queue'; import { AlchemyApiConfig, TenderlyApiConfig, TransactionManagerQueueItem } from './types'; import { TransactionReceipt, TransactionResponse, } from '@ethersproject/abstract-provider'; import { TransactionManagerResponse } from './transaction-inputs/transaction-manager-response'; import { TransactionGasOptimizer } from './optimizer/gas-optimizer'; import { TransactionStarted } from './events'; // class to perform transaction execution export class TransactionExecManager { private queueHandler: TransactionQueueHandler; private provider: providers.Provider; private transactionErrorHandler: TransactionErrorHandler; private gasOptimizer: TransactionGasOptimizer; private wallet: ethers.Wallet; private txTimeoutInSeconds: number; private gasMultiplier: number; constructor( queueHandler: TransactionQueueHandler, provider: ethers.providers.Provider, wallet: ethers.Wallet, txTimeoutInSeconds: number, gasMultiplier: number, tenderlyApiConfig: TenderlyApiConfig, alchemyConfig: AlchemyApiConfig, ) { this.queueHandler = queueHandler; this.provider = provider; this.gasMultiplier = gasMultiplier; this.gasOptimizer = new TransactionGasOptimizer( this.provider, this.gasMultiplier, tenderlyApiConfig, alchemyConfig ); this.transactionErrorHandler = new TransactionErrorHandler( this.queueHandler, this.gasOptimizer, this.provider, ); this.gasOptimizer.updateTransactionErrorHandler( this.transactionErrorHandler, ); this.wallet = wallet; this.txTimeoutInSeconds = txTimeoutInSeconds; } public async initialize(chainId: number): Promise { await this.gasOptimizer.initialize(chainId); } public getGasOptimizer(): TransactionGasOptimizer { return this.gasOptimizer; } // process transaction queue item public async processTransactionQueueItem( transactionQueueItem: TransactionManagerQueueItem, isLiveTransaction = false, ): Promise { // get transaction response let transactionResponse: TransactionResponse | undefined; try { // get transaction transactionQueueItem = await this.gasOptimizer.optimize( transactionQueueItem, ); const transaction: PopulatedTransaction = transactionQueueItem.transaction; // events object const events = transactionQueueItem.transactionManagerResponse.events; events.transactionStarted({ transactionRequest: transaction, transactionId: transactionQueueItem.id, } as TransactionStarted); process.env.DEBUG == 'true' && console.log('Optimized transaction pushed :', transaction); const singedTransaction = await this.wallet.signTransaction(transaction); // send transaction transactionResponse = await this.provider.sendTransaction( singedTransaction, ); // Transaction confirmed event events.transactionConfirmed({ transactionRequest: transaction, transactionResponse: transactionResponse, transactionId: transactionQueueItem.id, }); // wait for transaction to be mined const transactionReceipt: TransactionReceipt = await this.provider.waitForTransaction( transactionResponse.hash, 1, this.txTimeoutInSeconds * 1000, ); return await this.handleTransactionExecuted( transactionQueueItem, isLiveTransaction, transactionReceipt, ); } catch (error) { console.log( '🚀 ~ file: transaction-exec-manager.ts:94 ~ TransactionExecManager ~ error:', error, ); // Remove transaction from live queue if exists if (isLiveTransaction) { this.queueHandler.removeFromLiveQueue(transactionQueueItem); } else { this.queueHandler.removeFromPendingQueue(transactionQueueItem); } return await this.transactionErrorHandler.handleError( error, transactionQueueItem, ); } } // handle transaction executed private async handleTransactionExecuted( transactionQueueItem: TransactionManagerQueueItem, isLiveTransaction = false, transactionReceipt: TransactionReceipt, ): Promise { const transaction = transactionQueueItem.transaction; const response = transactionQueueItem.transactionManagerResponse; const events = response.events; // send transaction executed status events.transactionExecuted({ transactionRequest: transaction, transactionReceipt: transactionReceipt, transactionId: transactionQueueItem.id, }); if (isLiveTransaction) { this.queueHandler.removeFromLiveQueue(transactionQueueItem); } else { this.queueHandler.removeFromPendingQueue(transactionQueueItem); } return response; } }