Where does it stop?
The node refuses the second transaction at its check, so nothing new reaches the mempool. The first transaction is still waiting, untouched.
Why does a replacement need to pay more?
A node keeps one transaction for each sender and nonce. A new one with the same nonce can take its place, but only if it pays enough more.
The rule is the client’s. geth’s default is a bump of 10% on both fee fields (txpool.pricebump), and a replacement has to clear the threshold on the fee cap and on the tip.
Why does it happen?
- A retry sends the same fees again. A timeout handler resends the transaction unchanged while the first one is still waiting.
- A speed-up raises only one fee field.
maxFeePerGasgoes up andmaxPriorityFeePerGasstays, or the other way round. - The bump is smaller than the node wants. You added 5% where the node asks for 10%.
How do I fix it?
- Wait if you can. If the fees were fine, the first transaction will be mined and a resend is not needed.
- To speed it up, send the same transaction again with both fee fields at least 10% higher. The rule is the node’s, so if 10% is refused, check what yours requires.
- To cancel it, send 0 to yourself with the same nonce and a higher fee:
import { createWalletClient, http, parseGwei } from 'viem';import { mainnet } from 'viem/chains';
declare const account: `0x${string}`;declare const nonce: number; // the nonce of the transaction that is still waitingconst client = createWalletClient({ account, chain: mainnet, transport: http() });
// Same nonce, both fee fields at least 10% higher than the waiting transaction's (2 and 1 gwei here).await client.sendTransaction({ to: account, // sending 0 to yourself cancels the first transaction instead of repeating it value: 0n, nonce, maxFeePerGas: parseGwei('2.4'), maxPriorityFeePerGas: parseGwei('1.2'),});If a wait for the first transaction had already read it, viem’s waitForTransactionReceipt notices the replacement and resolves with the receipt of the transaction that took its place; if the replacement came first, the wait can run into its timeout instead (why). On a block explorer the first one then shows as dropped and replaced.
What does it look like in a trace?
The refused attempt is a send span that ends with an error and no hash, like every refusal by the node. viem has no name for this one, so error.type is TransactionRejectedRpcError (see nonce too low for how to see the node’s wording). An accepted replacement looks different. In the run above, with the cancel sent 500 ms after the wait started, the cancel replaced the waiting transaction and hashspan recorded both:
- blockchain.tx.status
- replaced
- blockchain.tx.replacement.hash
- the hash of the cancel transaction
The original span ends as replaced, with the hash of the transaction that took its place, and the replacement has its own confirm span with the outcome. How the receipt is attributed is in the transaction lifecycle.