Nonce management

Understand how Arbitrum handles nonces: the sequencer keeps a short retry window instead of a pending pool, so concurrent submitters must coordinate nonces, and a resubmitted nonce can sometimes return with already known or replacement transaction underpriced.

Request an update

Arbitrum does not maintain a long-lived "pending" mempool as an Ethereum node does. Transactions submitted to the Sequencer are ordered and executed almost immediately, so out-of-order nonces cannot wait minutes or hours for their predecessor to arrive — they are held only briefly, then rejected.

This page explains how Arbitrum's sequencer handles out-of-order nonces, the error you will see when the wait window expires, what eth_getTransactionCount("pending") returns, and the submission patterns that work safely under these constraints.

How Arbitrum handles out-of-order nonces

When an Ethereum execution client receives a transaction whose nonce is higher than the sender's current state nonce, it places the transaction in its pending pool and keeps it there for an extended period (typically configured in minutes-to-hours, sometimes longer) while it waits for the missing predecessor transaction(s) to arrive. Concurrent submitters routinely rely on this behavior.

Arbitrum's sequencer behaves differently. It maintains an in-memory nonce-failure retry buffer rather than a long-lived pending pool:

  1. When a transaction reaches execution with a nonce higher than the sender's state nonce (the next nonce the account expects, one more than the last executed nonce), the sequencer does not reject it immediately. Instead, it places the transaction in the retry buffer and starts a short expiry timer. A transaction whose nonce equals the state nonce is in order and executes, even if an earlier one is still queued.
  2. If the predecessor transaction arrives before the timer fires, the held transaction is revived and put back into the execution queue—it goes through normally, as if it had arrived in order.
  3. If the timer fires first, the held transaction is dropped, and the sender receives a nonce too high error in response to its original eth_sendRawTransaction call.

The animation below shows step 2. Transactions from other senders pass through the queue in arrival order. Your transaction with nonce 8 arrives before nonce 7, so the sequencer holds it in the retry buffer and starts the timer. Nonce 7 arrives inside the window and executes, and the sequencer then revives nonce 8 and executes it next.

Animated diagram of the sequencer's nonce retry buffer. Transactions from other senders enter the sequencer queue from the left and land in a block on the right. Your transaction with nonce 8 arrives before nonce 7, so the sequencer moves it into the retry buffer and starts the expiry timer. Nonce 7 arrives while the timer is still running and executes. The sequencer then revives nonce 8 from the buffer and executes it next.

The exact wait window and buffer capacity are configured by the operator running the sequencer. On Arbitrum One, the window is on the order of seconds, not minutes—short enough that you should treat it as a tolerance for network jitter and tx-ordering races, not as a substitute for the Ethereum pending pool.

The practical implication is that fire-and-forget concurrent submission patterns that work on Ethereum will not work reliably on Arbitrum. If you submit transactions N and N+1 from two different processes at the same time, you cannot rely on the sequencer holding N+1 while it waits for N to land — the window is too short.

The error you will see

When the retry window expires without the predecessor arriving, the call that submitted the out-of-order transaction returns the error string:

nonce too high

This is the same literal error string used by Ethereum's execution layer, but the meaning is different:

  • On Ethereum, nonce too high usually means the gap between the sender’s current nonce and the pending pool's buffer is larger than the pending pool can or will buffer (often a multi-thousand-slot gap, configurable per client).
  • On Arbitrum, nonce too high simply means the predecessor transaction did not reach the sequencer within the retry window.

The animation below shows step 3. Nonce 8 arrives before nonce 7 and waits in the retry buffer. The timer runs out before nonce 7 arrives, so the sequencer drops nonce 8 and returns nonce too high to your sender. When nonce 7 arrives late, it executes normally, because its nonce equals the state nonce. Nonce 8 is gone, so you must resend it.

Animated diagram of the sequencer's nonce retry buffer when the timer expires. Transactions from other senders enter the sequencer queue from the left and land in a block on the right. Your transaction with nonce 8 arrives before nonce 7, so the sequencer moves it into the retry buffer and starts the expiry timer. Nonce 7 does not arrive and the timer runs out. The sequencer drops nonce 8 and returns the error nonce too high to your sender. Nonce 7 then arrives late and executes normally, so your sender must resend nonce 8.

A typical log line from a real out-of-order submission looks like:

reqid=… err="nonce too high"

If you see this in production after migrating an Ethereum-based submission pipeline to Arbitrum, the most likely cause is that two or more workers are submitting transactions for the same sender address without coordinating nonce allocation between them—not that any individual nonce is wrong.

eth_getTransactionCount("pending") on Arbitrum

On an Ethereum node, eth_getTransactionCount(address, "pending") returns a value that includes any transactions currently sitting in the pending pool waiting to be mined. Many concurrent-submission designs rely on this to allocate the next nonce.

On Arbitrum, eth_getTransactionCount(address, "pending") returns the same value as eth_getTransactionCount(address, "latest"). The sequencer's retry buffer is an internal sequencer state and is not exposed through the RPC interface, so there is no way for an external caller to observe whether a higher-nonce transaction is currently being held. Services that previously relied on "pending" to coordinate concurrent submitters should track outstanding nonces themselves, using receipts or eth_getTransactionCount(address, "latest"), rather than fetching them using eth_getTransactionCount(address, "pending").

Submitting a transaction with a nonce that is already in flight

While a transaction is in flight (queued, or held for a nonce gap), the sequencer rejects a second transaction from the same sender with the same nonce at admission, with one of two errors. The one exception is a PGA chain, where a copy that raises the priority fee enough is admitted instead; the table below covers it. Both error strings are the ones Ethereum clients use, but on Arbitrum the window behind them is seconds, not hours, and there is no mempool you can query to see the first transaction.

already known: the same transaction twice

You get already known when the sequencer receives a transaction it already holds: same sender, same nonce, same transaction hash. As on Ethereum, the error means the transaction was received and is held, but has not executed and is not in a block.

What differs from Ethereum is the hold:

  • The sequencer holds the transaction for seconds: up to queue-timeout if it is queued, or a short buffer on the order of seconds if it is parked behind a nonce gap. It never holds it for hours.
  • There is no pending pool to query, so you cannot check whether the first copy is still in flight.

Don't treat already known as proof that the nonce is consumed. The receipt of the first copy is what gives you a clear signal:

  • If you receive a receipt, with status success or reverted, the transaction executed and consumed the nonce. Use N + 1 next.
  • If no receipt arrives after the hold window has passed (queue-timeout plus the nonce-gap buffer), the sequencer dropped the transaction and the nonce is still free. Resend the same signed bytes at the same nonce.

Drops are more frequent when the chain is busy. A queued transaction that waits longer than queue-timeout is dropped. The base fee can also rise above the transaction's max fee per gas before it executes, which makes the transaction invalid.

Do not increment the nonce on the retry: if the first copy was dropped, nothing used the nonce, and a transaction at the next nonce is held for the nonce-gap window and then dropped with nonce too high.

How viem reports this error

viem surfaces already known as NonceTooLowError. The name is misleading here: the nonce is not consumed, and a retry at nonce + 1 is dropped with nonce too high if the first copy was dropped.

replacement transaction underpriced: a different transaction, same nonce

You get replacement transaction underpriced when you submit a transaction that has the same sender and nonce as one in flight, but a different hash, and the sequencer rejects it. The sequencer returns the error at admission; the new transaction is rejected, and the first one still stands. On an FCFS chain every such transaction is rejected. On a PGA chain, only one whose priority fee is not high enough to compete is rejected.

What happens next depends on the chain's :

Ordering policyWhat a same-nonce, different-transaction-hash resubmission does
Returns replacement transaction underpriced error while the first transaction is in flight. No fee bump will replace an alraedy queued transaction. A bump-and-retry loop, which works on Ethereum, does not succeed here.

Replaces the first transaction only if it enters the queue before the first transaction executes and outbids the first transaction's plus the virtual boost the first transaction has accumulated while waiting by 10%. A copy that is admitted but loses in the subsequent auction because the first transaction already got executed will return nonce too low, not with replacement transaction underpriced.

On an FCFS chain, wait for the receipt of the first transaction to increment the nonce. If you want a different transaction to go through, submit it at the next nonce. On a PGA chain, wait for a receipt at that nonce, from either hash, to confirm which copy landed.

How ethers.js reports this error

ethers.js classifies replacement transaction underpriced as REPLACEMENT_UNDERPRICED.

Resync your nonce

After either error, take your next nonce from the chain, not from a local counter:

  • When you receive the receipt for the transaction at nonce N, use N + 1, or
  • Call eth_getTransactionCount(address, "latest"). On Arbitrum, "pending" returns the same value, so it adds nothing.

A locally incremented nonce drifts as soon as one in-flight transaction is dropped, and every later transaction is then held for the nonce-gap window and dropped with nonce too high.

Further reading

How is this guide?

On this page