From e1c9731c9082af38b6a80af6c3536a4b670064d6 Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Thu, 29 Jan 2026 22:07:59 +0530 Subject: [PATCH 1/2] added a support for send transaction sync --- src/modules/chains/chains.service.ts | 4 + src/modules/chains/interfaces.ts | 3 + src/modules/chains/schemas.ts | 2 + src/modules/executor/constants.ts | 5 + src/modules/executor/executor.processor.ts | 440 ++++++++++++------ src/modules/executor/interfaces.ts | 12 + src/modules/rpc-manager/classify-error.ts | 8 + .../rpc-manager/rpc-manager.service.ts | 37 ++ 8 files changed, 378 insertions(+), 133 deletions(-) diff --git a/src/modules/chains/chains.service.ts b/src/modules/chains/chains.service.ts index 0449a885..fa56719d 100644 --- a/src/modules/chains/chains.service.ts +++ b/src/modules/chains/chains.service.ts @@ -121,6 +121,10 @@ export class ChainsService this.rpcManagerService.executeRequest(chainId, (client) => { return client.debug.traceCallSupported; }), + // This is not used as part of health check but we're fetching this value during node intialization for a warm up + this.rpcManagerService.executeRequest(chainId, (client) => { + return client.transaction.sendTransactionSyncSupported; + }), ]); let state: HealthCheckState; diff --git a/src/modules/chains/interfaces.ts b/src/modules/chains/interfaces.ts index 5884f0ff..421b1f39 100644 --- a/src/modules/chains/interfaces.ts +++ b/src/modules/chains/interfaces.ts @@ -94,6 +94,9 @@ export interface ChainClientExtended { hash: Hex, ): Promise; }; + transaction: { + sendTransactionSyncSupported: Promise; + }; debug: { traceCallSupported: Promise; traceCall( diff --git a/src/modules/chains/schemas.ts b/src/modules/chains/schemas.ts index 7aaca71c..130066bc 100644 --- a/src/modules/chains/schemas.ts +++ b/src/modules/chains/schemas.ts @@ -1,6 +1,7 @@ import { addressSchema, bigIntLikeSchema, + booleanSchema, etherSchema, hexSchema, intLikeSchema, @@ -89,6 +90,7 @@ const chainConfigSchemaBase = z.object({ }) .default({}), isTestChain: z.boolean().default(false), + isLowBlockTimeChain: booleanSchema.default(false), contracts: chainConfigContractsSchema.default({}), price: chainConfigPriceSchema, paymentTokens: z.array(chainConfigPaymentTokenSchema).nonempty(), diff --git a/src/modules/executor/constants.ts b/src/modules/executor/constants.ts index 703da0a7..caa4bf9c 100644 --- a/src/modules/executor/constants.ts +++ b/src/modules/executor/constants.ts @@ -45,3 +45,8 @@ export const BLOCK_GAS_LIMIT_EXCEEDS_ERROR_MESSAGES = [ "exceeds block gas limit", "block gas limit exceeds", ]; + +export const TRANSACTION_EXECUTION_SYNC_ERROR_MESSAGES = [ + "The transaction was added to the mempool but wasn't processed", + "Invalid transaction", +]; diff --git a/src/modules/executor/executor.processor.ts b/src/modules/executor/executor.processor.ts index 382f209f..ddcc88a7 100644 --- a/src/modules/executor/executor.processor.ts +++ b/src/modules/executor/executor.processor.ts @@ -41,11 +41,13 @@ import { PRIORITY_FEE_ERROR_MESSAGES, REPLACEMENT_TRANSACTION_GAS_PRICE_ERROR_MESSAGES, TIME_OUT_ERROR_MESSAGES, + TRANSACTION_EXECUTION_SYNC_ERROR_MESSAGES, } from "./constants"; import { EXECUTOR_QUEUE_JOB_ATTEMPTS } from "./executor.config"; import { ExecutorService } from "./executor.service"; import { BumpGasAndNonceOptions, + type ExecuteBlockResponse, ExecuteOptions, type ExecutorJob, type ExecutorTxRequest, @@ -682,16 +684,15 @@ export class ExecutorProcessor implements Processor { meeUserOps: SignedPackedMeeUserOp[], authorizationList: EIP7702Auth[], options: ExecuteOptions, - ) { + ): Promise { const { chainId, - chainSettings: { executionOverrides }, + chainSettings: { executionOverrides, isLowBlockTimeChain }, } = this.chainsService; - let transactionReceipt: TransactionReceipt | undefined; - const entryPointV7Abi = this.contractsService.getContractAbi("entryPointV7"); + const entryPointV7Address = this.contractsService.getContractAddress( "entryPointV7", chainId, @@ -735,10 +736,127 @@ export class ExecutorProcessor implements Processor { }; } - let txHash: Hex; + // This value is fetched only once during the node initialization for all the RPC providers and will be reused. So no extra RPC call here + const sendTransactionSyncSupported = + await this.rpcManagerService.executeRequest(chainId, (client) => { + return client.transaction.sendTransactionSyncSupported; + }); + + if (isLowBlockTimeChain && sendTransactionSyncSupported) { + try { + // Tx receipt with just 1 block confirmation + const instantTransactionReceipt = await withTrace( + "executionPhase.executeBlockSync", + async () => + await this.rpcManagerService.executeRequest( + chainId, + (chainClient) => { + return chainClient + .connectAccount(this.nodeAccount) + .writeContractSync({ + address: entryPointV7Address, + abi: entryPointV7Abi, + functionName: "handleOps", + args: [ + meeUserOps.map(({ userOp }) => ({ ...userOp })), + this.nodeAccount.address, + ], + chain: chainClient.chain, + account: this.nodeAccount, + ...txRequest, + ...(executionOverrides.gas + ? { gas: executionOverrides.gas } + : {}), + // Half of the original timeout value + timeout: + this.chainsService.chainSettings + .waitConfirmationsTimeout / 2, + }); + }, + ), + { chainId, batchHash }, + )(); + + const txHash = instantTransactionReceipt.transactionHash; + + this.logger.trace( + { + chainId, + batchHash, + meeUserOpHashes: meeUserOps.map( + (meeUserOp) => meeUserOp.meeUserOpHash, + ), + eoaWorkerAddress: this.nodeAccount.address, + txHash, + isLowBlockTimeChain, + }, + "Userop block execution sync txHash with txReceipt", + ); + + // If the instant transaction receipt is fetched. It guaruntees that the transaction has been mined irrespective of whether it is successful or reverted on chain. + // So we mark the nonce as used and increase the current nonce to sync the cache in nonce manager to have a fresh nonce for next executions for this worker + this.nonceManagerService.markNonceAsUsed( + this.nodeAccount.address, + chainId, + nonce, + ); + + // This will skip the quick fast block status update + if (instantTransactionReceipt.status === "reverted") { + throw new SomethingWentWrongException( + "Block reverted, failed to execute userOps", + ); + } + + // Quick status update for faster block inclusion result but not reliable + this.handleSupertransactionExecutionResult( + batchHash, + meeUserOps, + txHash, + instantTransactionReceipt, + false, + ); + + const defaultBlockTxReceiptResult = + await this.handleDefaultBlockTxReceipt( + batchHash, + meeUserOps, + txHash, + nonce, + ); + + if ( + defaultBlockTxReceiptResult.isError !== true && + defaultBlockTxReceiptResult.transactionReceipt + ) { + // This will end up as non retriable error. As a result, the entire userOp list is simulated again + // and faulty userOps will be removed and retried with a new job. + if ( + defaultBlockTxReceiptResult.transactionReceipt.status === "reverted" + ) { + throw new SomethingWentWrongException( + "Block reverted, failed to execute userOps", + ); + } + + return { + transactionReceipt: defaultBlockTxReceiptResult.transactionReceipt, + txHash: defaultBlockTxReceiptResult.txHash, + }; + } + + return defaultBlockTxReceiptResult; + } catch (error) { + return this.handleTransactionExecutionError( + batchHash, + meeUserOps, + error, + ); + } + } try { - txHash = await withTrace( + const txHash = await withTrace( "executionPhase.executeBlock", async () => await this.rpcManagerService.executeRequest( @@ -784,7 +902,7 @@ export class ExecutorProcessor implements Processor { try { // By default one block confirmation is considered. For flashblock, we don't have to handle anything special. The RPC nodes will // show as 1 block confirmation if the tx is already available in flash block. But the tx can be reorged later in worst case - transactionReceipt = await withTrace( + const transactionReceipt = await withTrace( "executionPhase.fastBlockTxReceipt", async () => { // Fallback RPC providers are skipped here to avoid timeout retries on all fallbacks which is time consuming @@ -810,18 +928,16 @@ export class ExecutorProcessor implements Processor { { chainId, batchHash }, )(); - if (transactionReceipt) { - // If the transaction receipt is fetched. It guaruntees that the transaction has been mined irrespective of whether it is successful or reverted on chain. - // So we mark the nonce as used and increase the current nonce to sync the cache in nonce manager to have a fresh nonce for next executions for this worker - this.nonceManagerService.markNonceAsUsed( - this.nodeAccount.address, - chainId, - nonce, - ); - } + // If the transaction receipt is fetched. It guaruntees that the transaction has been mined irrespective of whether it is successful or reverted on chain. + // So we mark the nonce as used and increase the current nonce to sync the cache in nonce manager to have a fresh nonce for next executions for this worker + this.nonceManagerService.markNonceAsUsed( + this.nodeAccount.address, + chainId, + nonce, + ); // This will skip the quick fast block status update - if (transactionReceipt?.status === "reverted") { + if (transactionReceipt.status === "reverted") { throw new SomethingWentWrongException( "Block reverted, failed to execute userOps", ); @@ -855,74 +971,164 @@ export class ExecutorProcessor implements Processor { ); } - try { - transactionReceipt = await withTrace( - "executionPhase.defaultBlockTxReceipt", - async () => { - // Fallback RPC providers are skipped here to avoid timeout retries on all fallbacks which is time consuming - const { client } = - this.rpcManagerService.getPrimaryRpcProvider(chainId); - - if (!client) - throw new SomethingWentWrongException( - "Failed to fetch primary RPC provider public client", - ); - - return client.waitForTransactionReceipt({ - hash: txHash, - pollingInterval: - this.chainsService.chainSettings.executor.pollInterval, - confirmations: this.chainsService.chainSettings.waitConfirmations, - timeout: - this.chainsService.chainSettings.waitConfirmationsTimeout, - retryCount: 3, - }); - }, - { chainId, batchHash }, - )(); + const defaultBlockTxReceiptResult = + await this.handleDefaultBlockTxReceipt( + batchHash, + meeUserOps, + txHash, + nonce, + ); - if (transactionReceipt) { - // If the transaction receipt is fetched. It guaruntees that the transaction has been mined irrespective of whether it is successful or reverted on chain. - // So we mark the nonce as used and increase the current nonce to sync the cache in nonce manager to have a fresh nonce for next executions for this worker - this.nonceManagerService.markNonceAsUsed( - this.nodeAccount.address, - chainId, - nonce, + if ( + defaultBlockTxReceiptResult.isError !== true && + defaultBlockTxReceiptResult.transactionReceipt + ) { + // This will end up as non retriable error. As a result, the entire userOp list is simulated again + // and faulty userOps will be removed and retried with a new job. + if ( + defaultBlockTxReceiptResult.transactionReceipt.status === "reverted" + ) { + throw new SomethingWentWrongException( + "Block reverted, failed to execute userOps", ); } - } catch (error) { - if (error instanceof Error) { - const err = error as Error; - const { errorType, isRetriableError } = this.parseExecutionError( - err.message, - ); + return { + transactionReceipt: defaultBlockTxReceiptResult.transactionReceipt, + txHash: defaultBlockTxReceiptResult.txHash, + }; + } - // if the error is because of timeout, the tx might not be mined yet. So we consider this as a retriable error. - // so the block will be immediately retried. - if ( - isRetriableError === true && - errorType === - USER_OP_EXECUTION_ERRORS.TRANSACTION_RECEIPT_TIMEOUT_ERROR - ) { - this.logger.error( - { - chainId, - batchHash, - meeUserOpHashes: meeUserOps.map( - (meeUserOp) => meeUserOp.meeUserOpHash, - ), - eoaWorkerAddress: this.nodeAccount.address, - errorType: USER_OP_ERROR_MESSAGES[errorType], - isRetriableError, - errorMessage: err.message, - }, - "Failed to fetch transaction receipt due to timeout error", + return defaultBlockTxReceiptResult; + } catch (error) { + return this.handleTransactionExecutionError(batchHash, meeUserOps, error); + } + } + + handleTransactionExecutionError( + batchHash: Hash, + meeUserOps: SignedPackedMeeUserOp[], + error: unknown, + ): ExecuteBlockResponse { + const { + chainId, + chainSettings: { isLowBlockTimeChain }, + } = this.chainsService; + + if ( + error instanceof Error || + error instanceof SomethingWentWrongException || + error instanceof BadRequestException || + error instanceof ContractFunctionExecutionError || + error instanceof ContractFunctionRevertedError + ) { + const err = error as Error; + + const { errorType, isRetriableError } = this.parseExecutionError( + err.message, + ); + + this.logger.error( + { + chainId, + batchHash, + meeUserOpHashes: meeUserOps.map( + (meeUserOp) => meeUserOp.meeUserOpHash, + ), + eoaWorkerAddress: this.nodeAccount.address, + errorType: USER_OP_ERROR_MESSAGES[errorType], + isRetriableError, + errorMessage: error.message, + isLowBlockTimeChain, + }, + "Failed to execute userOps", + ); + + return { isError: true, errorType, isRetriableError }; + } + + this.logger.error( + { + chainId, + batchHash, + meeUserOpHashes: meeUserOps.map((meeUserOp) => meeUserOp.meeUserOpHash), + eoaWorkerAddress: this.nodeAccount.address, + errorType: + USER_OP_ERROR_MESSAGES[USER_OP_EXECUTION_ERRORS.UNRECOGNIZED_ERROR], + error: stringify(error), + isRetriableError: false, + isLowBlockTimeChain, + }, + "Failed to execute userOps", + ); + + return { + isError: true, + errorType: USER_OP_EXECUTION_ERRORS.UNRECOGNIZED_ERROR, + isRetriableError: false, + }; + } + + async handleDefaultBlockTxReceipt( + batchHash: Hash, + meeUserOps: SignedPackedMeeUserOp[], + txHash: Hex, + nonce: number, + ): Promise { + const { + chainId, + chainSettings: { isLowBlockTimeChain }, + } = this.chainsService; + + try { + const transactionReceipt = await withTrace( + "executionPhase.defaultBlockTxReceipt", + async () => { + // Fallback RPC providers are skipped here to avoid timeout retries on all fallbacks which is time consuming + const { client } = + this.rpcManagerService.getPrimaryRpcProvider(chainId); + + if (!client) + throw new SomethingWentWrongException( + "Failed to fetch primary RPC provider public client", ); - return { isError: true, errorType, isRetriableError, txHash }; - } + return client.waitForTransactionReceipt({ + hash: txHash, + pollingInterval: + this.chainsService.chainSettings.executor.pollInterval, + confirmations: this.chainsService.chainSettings.waitConfirmations, + timeout: this.chainsService.chainSettings.waitConfirmationsTimeout, + retryCount: 3, + }); + }, + { chainId, batchHash }, + )(); + + // If the transaction receipt is fetched. It guaruntees that the transaction has been mined irrespective of whether it is successful or reverted on chain. + // So we mark the nonce as used and increase the current nonce to sync the cache in nonce manager to have a fresh nonce for next executions for this worker + this.nonceManagerService.markNonceAsUsed( + this.nodeAccount.address, + chainId, + nonce, + ); + + return { transactionReceipt, txHash }; + } catch (error) { + if (error instanceof Error) { + const err = error as Error; + const { errorType, isRetriableError } = this.parseExecutionError( + err.message, + ); + + // if the error is because of timeout, the tx might not be mined yet. So we consider this as a retriable error. + // so the block will be immediately retried. + if ( + isRetriableError === true && + errorType === + USER_OP_EXECUTION_ERRORS.TRANSACTION_RECEIPT_TIMEOUT_ERROR + ) { this.logger.error( { chainId, @@ -934,55 +1140,14 @@ export class ExecutorProcessor implements Processor { errorType: USER_OP_ERROR_MESSAGES[errorType], isRetriableError, errorMessage: err.message, + isLowBlockTimeChain, }, - "Failed to fetch transaction receipt", + "Failed to fetch transaction receipt due to timeout error", ); - // If it is failed due to any other reasons like RPC issue ? It will be treated as mentioned below - return { transactionReceipt: undefined, txHash }; + return { isError: true, errorType, isRetriableError, txHash }; } - this.logger.error( - { - chainId, - batchHash, - meeUserOpHashes: meeUserOps.map( - (meeUserOp) => meeUserOp.meeUserOpHash, - ), - eoaWorkerAddress: this.nodeAccount.address, - error: stringify(error), - }, - "Failed to fetch transaction receipt", - ); - // If transaction receipt failed to fetch due to RPC issues ? All the userOps are forcefully marked as successful. - // There is no way to identify whether userOp is executed or not without checking tx receipt. So it is always - // better to mark the tx as success than failure. Sending undefined will mark userOps as success down the line. - return { transactionReceipt: undefined, txHash }; - } - - // This will end up as non retriable error. As a result, the entire userOp list is simulated again - // and faulty userOps will be removed and retried with a new job. - if (transactionReceipt.status === "reverted") { - throw new SomethingWentWrongException( - "Block reverted, failed to execute userOps", - ); - } - - return { transactionReceipt, txHash }; - } catch (error) { - if ( - error instanceof Error || - error instanceof SomethingWentWrongException || - error instanceof BadRequestException || - error instanceof ContractFunctionExecutionError || - error instanceof ContractFunctionRevertedError - ) { - const err = error as Error; - - const { errorType, isRetriableError } = this.parseExecutionError( - err.message, - ); - this.logger.error( { chainId, @@ -993,12 +1158,14 @@ export class ExecutorProcessor implements Processor { eoaWorkerAddress: this.nodeAccount.address, errorType: USER_OP_ERROR_MESSAGES[errorType], isRetriableError, - errorMessage: error.message, + errorMessage: err.message, + isLowBlockTimeChain, }, - "Failed to execute userOps", + "Failed to fetch transaction receipt", ); - return { isError: true, errorType, isRetriableError }; + // If it is failed due to any other reasons like RPC issue ? It will be treated as mentioned below + return { transactionReceipt: undefined, txHash }; } this.logger.error( @@ -1009,19 +1176,16 @@ export class ExecutorProcessor implements Processor { (meeUserOp) => meeUserOp.meeUserOpHash, ), eoaWorkerAddress: this.nodeAccount.address, - errorType: - USER_OP_ERROR_MESSAGES[USER_OP_EXECUTION_ERRORS.UNRECOGNIZED_ERROR], error: stringify(error), - isRetriableError: false, + isLowBlockTimeChain, }, - "Failed to execute userOps", + "Failed to fetch transaction receipt", ); - return { - isError: true, - errorType: USER_OP_EXECUTION_ERRORS.UNRECOGNIZED_ERROR, - isRetriableError: false, - }; + // If transaction receipt failed to fetch due to RPC issues ? All the userOps are forcefully marked as successful. + // There is no way to identify whether userOp is executed or not without checking tx receipt. So it is always + // better to mark the tx as success than failure. Sending undefined will mark userOps as success down the line. + return { transactionReceipt: undefined, txHash }; } } @@ -1282,6 +1446,16 @@ export class ExecutorProcessor implements Processor { } } + // Transaction execution sync errors will be handled here + for (const configuredErrorMessage of TRANSACTION_EXECUTION_SYNC_ERROR_MESSAGES) { + if (errorMessage.includes(configuredErrorMessage.toLowerCase())) { + return { + isRetriableError: true, + errorType: USER_OP_EXECUTION_ERRORS.TRANSACTION_EXECUTION_SYNC_ERROR, + }; + } + } + return { isRetriableError: false, errorType: USER_OP_EXECUTION_ERRORS.UNRECOGNIZED_ERROR, diff --git a/src/modules/executor/interfaces.ts b/src/modules/executor/interfaces.ts index 3399a387..7e35bfba 100644 --- a/src/modules/executor/interfaces.ts +++ b/src/modules/executor/interfaces.ts @@ -3,6 +3,7 @@ import { type HealthCheckDataWithChains } from "@/health-check"; import { type MeeUserOpBatch } from "@/user-ops"; import { type Hex, + type TransactionReceipt, type TransactionRequestEIP1559, type TransactionRequestEIP7702, type TransactionRequestLegacy, @@ -53,6 +54,7 @@ export enum USER_OP_EXECUTION_ERRORS { TRANSACTION_RECEIPT_TIMEOUT_ERROR = 7, BLOCK_GAS_LIMIT_EXCEEDS_ERROR = 8, REPLACEMENT_TRANSACTION_GAS_PRICE_TOO_LOW = 9, + TRANSACTION_EXECUTION_SYNC_ERROR = 10, } export const USER_OP_ERROR_MESSAGES: Record = @@ -75,6 +77,8 @@ export const USER_OP_ERROR_MESSAGES: Record = "Block gas limit exceeds", [USER_OP_EXECUTION_ERRORS.REPLACEMENT_TRANSACTION_GAS_PRICE_TOO_LOW]: "Replacement transaction gas price is too low for this transaction", + [USER_OP_EXECUTION_ERRORS.TRANSACTION_EXECUTION_SYNC_ERROR]: + "Failed to execute transaction from the mempool", }; export interface ExecuteOptions { @@ -87,3 +91,11 @@ export interface BumpGasAndNonceOptions { percentage: bigint; executeOptions: ExecuteOptions; } + +export type ExecuteBlockResponse = { + transactionReceipt?: TransactionReceipt; + txHash?: Hex; + isError?: boolean; + errorType?: USER_OP_EXECUTION_ERRORS; + isRetriableError?: boolean; +}; diff --git a/src/modules/rpc-manager/classify-error.ts b/src/modules/rpc-manager/classify-error.ts index 0dd7b122..66bb046c 100644 --- a/src/modules/rpc-manager/classify-error.ts +++ b/src/modules/rpc-manager/classify-error.ts @@ -6,6 +6,7 @@ import { PRIORITY_FEE_ERROR_MESSAGES, REPLACEMENT_TRANSACTION_GAS_PRICE_ERROR_MESSAGES, TIME_OUT_ERROR_MESSAGES, + TRANSACTION_EXECUTION_SYNC_ERROR_MESSAGES, } from "@/executor/constants"; import { CallExecutionError, @@ -156,6 +157,13 @@ export const classifyError = (error: any): RpcErrorType => { return RpcErrorType.RETRIABLE; } + // Transaction execution sync errors + if ( + matchesErrorPattern(errorMessage, TRANSACTION_EXECUTION_SYNC_ERROR_MESSAGES) + ) { + return RpcErrorType.CHAIN_ERROR; + } + // ========= 4. NON-RETRIABLE CLIENT ERRORS (4xx except 408/429) ========= // HttpRequestError: handle status codes diff --git a/src/modules/rpc-manager/rpc-manager.service.ts b/src/modules/rpc-manager/rpc-manager.service.ts index 55ebef23..cd5d307f 100644 --- a/src/modules/rpc-manager/rpc-manager.service.ts +++ b/src/modules/rpc-manager/rpc-manager.service.ts @@ -92,6 +92,7 @@ export class RpcManagerService { transport, }).extend((client) => { let traceCallSupported: Promise | undefined; + let sendTransactionSyncSupported: Promise | undefined; // Mocaverse chain has some RPC interface issue for tracerConfig field, this is a temporary workwround and it will be removed soon const isStringifiedRpcPayloadRequired = [222888, 2288].includes( @@ -117,6 +118,10 @@ export class RpcManagerService { }, } as ChainClientExtended["debug"]; + const transaction = { + sendTransactionSyncSupported: Promise.resolve(false), + } as ChainClientExtended["transaction"]; + const trace = { transaction: async (hash: Hex) => { if (!client.transport.url) { @@ -180,6 +185,37 @@ export class RpcManagerService { }, } as ChainClientExtended["trace"]; + Object.defineProperty(transaction, "sendTransactionSyncSupported", { + get() { + if (!sendTransactionSyncSupported) { + sendTransactionSyncSupported = client + .sendRawTransactionSync({ + serializedTransaction: "0x", + }) + .then(() => true) + // biome-ignore lint/suspicious/noExplicitAny: allow any for this variable + .catch((error: any) => { + const errorMessage = error?.message?.toLowerCase?.() || ""; + const errorCode = error?.code; + + // Method not found = not supported + if ( + errorCode === -32601 || + errorMessage.toLowerCase().includes("method not found") || + errorMessage.toLowerCase().includes("not supported") || + errorMessage.toLowerCase().includes("does not exist") + ) { + return false; + } + + return true; + }); + } + + return sendTransactionSyncSupported; + }, + }); + Object.defineProperty(debug, "traceCallSupported", { get() { if (!traceCallSupported) { @@ -218,6 +254,7 @@ export class RpcManagerService { return { debug, trace, + transaction, connectAccount, }; }); From cd9a681a7fbb9fa250948ec8497998252d4cb612 Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Thu, 29 Jan 2026 22:13:23 +0530 Subject: [PATCH 2/2] merged latest develop changes --- src/modules/executor/executor.processor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/executor/executor.processor.ts b/src/modules/executor/executor.processor.ts index df4e3eac..e866c4ed 100644 --- a/src/modules/executor/executor.processor.ts +++ b/src/modules/executor/executor.processor.ts @@ -782,6 +782,7 @@ export class ExecutorProcessor implements Processor { eoaWorkerAddress: this.nodeAccount.address, txHash, isLowBlockTimeChain, + ...txRequest, }, "Userop block execution sync txHash with txReceipt", );