Alert Source Discuss

Interface Draft Standards Track

EIP-7966: eth_sendRawTransactionSync Method

A JSON-RPC method to reduce transaction submission latency by allowing synchronous receipt of transaction hash and block inclusion.

Authors
Sam Battenally ()
,
Hai Nguyen ()
,
Thanh Nguyen ()
,
Loc Nguyen ()
Created 2025-06-11
Discussion Link https://ethereum-magicians.org/t/eip-7966-eth-sendrawtransactionsync-method/24640

Abstract

This EIP proposes a new JSON-RPC method, eth_sendRawTransactionSync, which submits a signed raw transaction and waits synchronously for the transaction receipt or a configurable timeout before returning. This method addresses the user experience gap in high-frequency applications by offering stronger delivery guarantees than eth_sendRawTransaction. Additionally, when a transaction cannot be immediately executed due to a nonce gap, it returns the expected nonce as a hex string in the error response, eliminating the need for additional RPC calls to query account state.

Motivation

Currently, Ethereum clients submit signed transactions asynchronously using eth_sendRawTransaction. Clients receive a transaction hash immediately but must poll repeatedly for the transaction receipt, which increases latency and complicates client-side logic.

This asynchronous approach is not efficient for high-frequency blockchains or Layer 2 solutions with fast block times and low latency, where rapid transaction throughput and quick confirmation feedback are critical. The need to separately poll for receipts results in increased network overhead, slower overall transaction confirmation feedback, and more complex client implementations.

Additionally, when transactions cannot be immediately executed (e.g., due to nonce gaps or insufficient funds), existing methods provide generic error messages that don't help developers understand or fix the issue. Developers must make additional RPC calls to query account state, creating unnecessary round-trips and delays.

Sync vs Async Transaction Sending In a low-latency blockchain, transaction receipts are often available right after the transactions land in the block producer’s mempool. Requiring an additional RPC call introduces unnecessary latency.

eth_sendRawTransactionSync addresses these issues by combining transaction submission and receipt retrieval into a single RPC call. This helps:

  • reduce total transaction submission and confirmation latency by approximately 50%;
  • simplify client implementations by eliminating the need for separate polling loops;
  • improve user experience by enabling more responsive dApps and wallets;
  • align blockchain interactions closer to traditional Web2 request-response patterns;
  • maintain backward compatibility and optionality, preserving existing RPC methods and semantics.

Specification

Method Name

eth_sendRawTransactionSync

Parameters

PositionTypeDescriptionRequired
1DATAThe signed transaction dataYes
2INTMaximum wait time in millisecondsNo

Parameter Validation Rules

  • Transaction Data. MUST be a valid hex-encoded, RLP-encoded signed transaction (same as in eth_sendRawTransaction).
  • Timeout. MUST be a positive integer not greater than the node-configured maximum timeout.

Returns

  • On success. Node implementations MUST return the transaction receipt object as defined by the eth_getTransactionReceipt method.
  • On timeout error. Node implementations MUST return an error code 4 with a timeout message.
  • On unreadiness error. Node implementations SHOULD return an error code 5 with an error message.
    • This happens when the processing node is not ready to accept a new transaction or the transaction is erroneous (DX improvement).
  • On standard error. Node implementations MUST return a JSON-RPC error object consistent with existing RPC error formats.

Error Codes and Response Structure

The following error codes are specific to eth_sendRawTransactionSync:

CodeError TypeDescriptionData Format
4TimeoutTransaction was added to mempool but not processed within timeoutTransaction hash (hex)
5Unknown/QueuedTransaction is NOT added to mempool (not ready for execution)Transaction hash (hex)
6Nonce GapTransaction is NOT added to mempool (nonce gap detected)Expected nonce (hex)

When an error occurs, the response includes:

  • code: The error code indicating the error type
  • message: A human-readable error message
  • data: Error-specific data:
    • For error code 4 (Timeout): Contains the transaction hash as a hex string (e.g., "0x1234abcd...")
    • For error code 5 (Unknown/Queued): Contains the transaction hash as a hex string (e.g., "0x1234abcd...")
    • For error code 6 (Nonce Gap): Contains the expected nonce as a hex string (e.g., "0x5")

Node-Configured Timeouts

  • The handler function of this RPC SHOULD incorporate a configurable timeout when waiting for receipts (RECOMMENDED: 2 seconds).
  • Node implementations SHOULD provide a way to configure the timeout duration.
  • Node operators MAY implement dynamic timeout adjustment based on real-time network conditions.

Behavior

Upon receiving an eth_sendRawTransactionSync request, the handler function performs the following tasks.

  • If timeout parameter is provided, the handler function MUST validate its validity.
    • If the timeout is invalid, the handler function MUST use the default node-configure timeout.
  • The handler function MUST check if the transaction is ready for immediate execution BEFORE adding it to the mempool:
    • If the transaction has a nonce gap (transaction nonce is higher than the expected nonce), the handler function MUST NOT add the transaction to the mempool and MUST return error code 6 with the expected nonce as a hex string directly in the data field.
    • If the transaction is queued for any other reason (not ready for immediate execution), the handler function MUST NOT add the transaction to the mempool and MUST return error code 5.
  • If the transaction is ready for immediate execution, the handler function MUST submit the signed transaction to the mempool as per the existing eth_sendRawTransaction semantics.
  • The handler function MUST wait for the transaction receipt until the timeout elapses.
  • If the receipt is found within the specified timeout, the handler function MUST return it immediately.
  • If the timeout expires without obtaining a receipt, the handler function MUST return an error code 4 with a timeout message and the transaction hash.
  • If the transaction submission fails (e.g., due to invalid transaction data), the handler function MUST return an error (following the eth_sendRawTransaction definition) immediately.

Example Request (No Timeout)

{
  "jsonrpc": "2.0",
  "method": "eth_sendRawTransactionSync",
  "params": [
    "0xf86c808504a817c80082520894ab... (signed tx hex)"
  ],
  "id": 1
}

Example Request (With Timeout)

{
  "jsonrpc": "2.0",
  "method": "eth_sendRawTransactionSync",
  "params": [
    "0xf86c808504a817c80082520894ab... (signed tx hex)",
    5000
  ],
  "id": 1
}

Example Response (Success)

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "transactionHash": "0x1234abcd...",
    "blockHash": "0xabcd1234...",
    "blockNumber": "0x10d4f",
    "cumulativeGasUsed": "0x5208",
    "gasUsed": "0x5208",
    "contractAddress": null,
    "logs": [],
    "status": "0x1"
  }
}

Example Response (Timeout - Error Code 4)

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": 4,
    "message": "The transaction was added to the mempool but wasn't processed within the designated timeout interval.",
    "data": "0x1234abcd..."
  }
}

Note: The data field contains the transaction hash as a hex string.

Example Response (Nonce Gap - Error Code 6)

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": 6,
    "message": "The transaction was rejected due to a nonce gap. Please resubmit with the next on-chain nonce.",
    "data": "0x5"
  }
}

Note: The data field contains the expected nonce as a hex string. No transaction hash is returned because the transaction was never added to the mempool.

Example Response (Rejected Transaction - Error Code 5)

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": 5,
    "message": "The transaction was rejected for an unknown reason.",
    "data": "0x1234abcd..."
  }
}

Example Response (Standard Error)

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32000,
    "message": "Invalid transaction"
  }
}

Rationale

Why Not Extend Existing RPC?

Modifying eth_sendRawTransaction to support this behavior would risk compatibility issues and ambiguity. A separate method makes the semantics explicit and opt-in.

Node-Configured Timeouts

Node implementations SHOULD allow configuration of the timeout period, defaulting to 2 seconds (depending on the implementation). This balances responsiveness and propagation guarantees without creating excessive overhead in node clients.

User-Configured Timeouts

The optional timeout parameter allows clients to specify their preferred maximum wait time for transaction processing.

  • Applications can adjust timeouts based on their specific latency requirements.
  • The optional timeout prevents the RPC call from blocking indefinitely.

Optionality

This method is optional and does not replace or change existing asynchronous transaction submission methods. Nodes that do not implement this method will continue to operate normally using the standard asynchronous RPC methods.

This RPC method is particularly suitable for EVM-compatible blockchains or L2 solutions with fast block times and low network latency, where synchronous receipt retrieval can significantly improve responsiveness. On high-latency or slower blockchains (e.g., Ethereum mainnet pre-sharding), the synchronous wait may cause longer RPC call durations or timeouts, making the method less practical.

Improved UX

The synchronous receipt retrieval reduces the complexity of client applications by eliminating the need for separate polling logic.

Returned Nonces in Error Code 6

When a transaction is rejected due to a nonce gap, error code 6 returns the expected nonce as a hex string in the data field. This design provides several benefits:

  • Immediate Nonce Recovery: Applications receive the correct nonce directly in the error response, eliminating the need for a separate eth_getTransactionCount call.
  • Reduced Latency: By avoiding additional RPC round-trips to query account state, applications can immediately construct and resubmit the transaction with the correct nonce.

Backwards Compatibility

This EIP introduces a new RPC method and does not modify or deprecate any existing methods. Nodes that do not implement this method will continue operating normally. Existing applications using eth_sendRawTransaction are unaffected. Node implementations that do not support the method will simply return method not found.

Reference Implementation

A minimal reference implementation can be realized by wrapping existing eth_sendRawTransaction submission with logic that waits for the corresponding transaction receipt until a timeout elapses. Implementations MAY either rely on event-driven receipt-availability notifications or poll eth_getTransactionReceipt at short intervals until a receipt is found or a timeout occurs. Polling intervals or notification strategies and timeout values can be tuned by node implementations to optimize performance.

For example, in reth, we can implement the handler for eth_sendRawTransactionSync as follows.

async fn send_raw_transaction_sync(
    &self,
    tx: Bytes,
    user_timeout_ms: Option<u64>,
) -> RpcResult<OpTransactionReceipt> {
    const MAX_TIMEOUT_MS: u64 = 2_000;

    const ERROR_CODE_TIMEOUT: i32 = 4;
    const ERROR_CODE_UNKNOWN: i32 = 5;
    const ERROR_CODE_NONCE_GAP: i32 = 6;

    const ERROR_MSG_TIMEOUT_RECEIPT: &str = "The transaction was added to the mempool but wasn't processed within the designated timeout interval.";
    const ERROR_MSG_UNKNOWN: &str = "The transaction was rejected for an unknown reason.";
    const ERROR_MSG_NONCE_GAP: &str = "The transaction was rejected due to a nonce gap. Please resubmit with the next on-chain nonce.";

    let start_time = Instant::now();
    let timeout = Duration::from_millis(
        user_timeout_ms.map_or(MAX_TIMEOUT_MS, |ms| ms.min(MAX_TIMEOUT_MS)),
    );

    let pool_transaction = OpPooledTransaction::from_pooled(recover_raw_transaction(&tx)?);
    let sender = pool_transaction.sender();

    let outcome = self
        .pool
        .add_transaction(TransactionOrigin::Local, pool_transaction)
        .await
        .map_err(OpEthApiError::from_eth_err)?;

    // If transaction is queued (not ready for immediate execution), remove it and return error
    if let AddedTransactionState::Queued(reason) = outcome.state {
        self.pool.remove_transaction(outcome.hash);

        match reason {
            QueuedReason::NonceGap => {
                let expected_nonce = self
                    .pending_state
                    .basic_account(&sender)
                    .ok()
                    .flatten()
                    .map(|acc| format!("0x{:x}", acc.nonce));

                return Err(ErrorObject::owned(
                    ERROR_CODE_NONCE_GAP,
                    ERROR_MSG_NONCE_GAP,
                    expected_nonce,
                ));
            }
            _ => {
                return Err(ErrorObject::owned(
                    ERROR_CODE_UNKNOWN,
                    ERROR_MSG_UNKNOWN,
                    Some(hash),
                ));
            }
        }
    }

    let hash = outcome.hash;
    match self
        .pending_state
        .get_receipt(hash, timeout.saturating_sub(start_time.elapsed()))
        .await
    {
        Some(receipt) => Ok(receipt),
        None => Err(ErrorObject::owned(
            ERROR_CODE_TIMEOUT,
            ERROR_MSG_TIMEOUT_RECEIPT,
            Some(hash),
        )),
    }
}

Other implementations such as go-ethereum can utilize a channel to signify receipt availability instead of polling.

Security Considerations

  • This method does not introduce new security risks beyond those inherent in transaction submission.
  • The node-configured timeout prevents indefinite blocking of RPC calls, protecting nodes from hanging requests.
  • Node implementations should handle timeout responses gracefully and continue monitoring transaction status as needed.
  • Nodes must ensure that the implementation does not degrade performance or cause denial-of-service.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Sam Battenally, Hai Nguyen, Thanh Nguyen, Loc Nguyen, "eth_sendRawTransactionSync Method," Ethereum Improvement Proposals, no. 7966, early access, June 2025. [Online serial]. Available: https://eips-wg.github.io/EIPs/7966/.