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
.
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.
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:
eth_sendRawTransactionSync
The parameters of this method MUST be identical to the eth_sendRawTransaction
method, which contain a signed transaction data.
DATA
. The signed transaction data.eth_getTransactionReceipt
method.-32002
with a timeout message.Upon receiving an eth_sendRawTransactionSync
request, the handler function performs the following tasks.
eth_sendRawTransaction
semantics.{
"jsonrpc": "2.0",
"method": "eth_sendRawTransactionSync",
"params": [
"0xf86c808504a817c80082520894ab... (signed tx hex)"
],
"id": 1
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"transactionHash": "0x1234abcd...",
"blockHash": "0xabcd1234...",
"blockNumber": "0x10d4f",
"cumulativeGasUsed": "0x5208",
"gasUsed": "0x5208",
"contractAddress": null,
"logs": [],
"status": "0x1"
}
}
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32002,
"message": "The transaction was added to the mempool but wasn't processed in 2s.",
"data": "0x1234abcd..."
}
}
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32000,
"message": "Invalid transaction"
}
}
Modifying eth_sendRawTransaction
to support this behavior would risk compatibility issues and ambiguity. A separate method makes the semantics explicit and opt-in.
Clients 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.
This method is optional and does not replace or change existing asynchronous transaction submission methods. Clients and servers 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.
The synchronous receipt retrieval reduces the complexity of client applications by eliminating the need for separate polling logic.
This EIP introduces a new RPC method and does not modify or deprecate any existing methods. Clients and servers that do not implement this method will continue operating normally. Existing applications using eth_sendRawTransaction
are unaffected. Clients that do not support the method will simply return method not found
.
A minimal reference implementation can be realized by wrapping existing eth_sendRawTransaction
submission with a polling loop that queries eth_getTransactionReceipt
at short intervals until a receipt is found or a timeout occurs. Polling intervals and timeout values can be tuned by client 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) -> RpcResult<OpTransactionReceipt> {
const TIMEOUT_DURATION: Duration = Duration::from_secs(2);
const POLL_INTERVAL: Duration = Duration::from_millis(1);
let hash = self.inner.send_raw_transaction(tx).await?;
let start = Instant::now();
while start.elapsed() < TIMEOUT_DURATION {
if let Some(receipt) = self.pending_block.get_receipt(hash) {
return Ok(receipt);
}
tokio::time::sleep(POLL_INTERVAL).await;
}
Err(ErrorObject::owned(
-32002,
format!(
"The transaction was added to the mempool but wasn't processed in {TIMEOUT_DURATION:?}."
),
Some(hash),
))
}
Other implementations such as go-ethereum
can utilize a channel to signify receipt availability instead of polling.
Copyright and related rights waived via CC0.