How to cancel a stuck Ethereum or Polygon transaction (same-nonce replacement)
Why a low-fee transaction blocks every transaction after it, and how to cancel or speed it up by sending a replacement with the same nonce, with a web3.py example.
A transaction that sits in “pending” for hours is one of the most common problems on EVM chains. It is also one of the easiest to fix once you understand nonces. I built a small tool for exactly this (Stuck Transaction Canceller); this post explains how it works.
Why one stuck transaction blocks everything
Every transaction from an account carries a nonce: a counter that starts at 0 and goes up by one for each transaction the account sends. The network processes an account’s transactions strictly in nonce order.
So if the transaction with nonce 42 was sent with a fee too low to be picked up, transactions 43, 44 and so on wait behind it, even if they pay a good fee. Your wallet looks frozen.
The fix: replace it with the same nonce
You can’t delete a pending transaction, but you can replace it. If you broadcast a new transaction with the same nonce and a higher fee, nodes drop the old one from their pool and keep the new one. Whichever transaction with that nonce gets mined first wins; the other becomes invalid.
That gives you two options:
- Speed up: resend the same transaction (same recipient, value and data) with a higher fee.
- Cancel: send a transaction to your own address with a value of
0, the same nonce and a higher fee. When it’s mined, the nonce is used up and the original can never execute.
How much higher does the fee need to be?
Nodes refuse a replacement that doesn’t pay meaningfully more. Geth, for example, uses a default price bump of 10% (--txpool.pricebump), and for EIP-1559 transactions both maxFeePerGas and maxPriorityFeePerGas have to go up. Other clients and RPC providers can be stricter, so in practice bump comfortably above that. On Polygon PoS, also check the network’s current minimum priority fee before you set yours.
Finding the nonce of the stuck transaction
The lowest unconfirmed nonce is the number of transactions the account has mined:
get_transaction_count(address, "latest")returns the next nonce the chain expects, which is the nonce of your oldest stuck transaction.get_transaction_count(address, "pending")also counts what’s sitting in the node’s pool. If it’s higher thanlatest, you have pending transactions.
A minimal web3.py example
This cancels the oldest pending transaction by sending 0 to yourself with the same nonce. Run it against a test network first.
from web3 import Web3
import os
w3 = Web3(Web3.HTTPProvider(os.environ["RPC_URL"]))
acct = w3.eth.account.from_key(os.environ["PRIVATE_KEY"]) # never hard-code keys
latest = w3.eth.get_transaction_count(acct.address, "latest")
pending = w3.eth.get_transaction_count(acct.address, "pending")
if pending == latest:
raise SystemExit("No pending transactions for this address.")
nonce = latest # oldest stuck transaction
# Look up the fees you originally used (e.g. from your logs or the explorer),
# then bump both well above the node's minimum replacement threshold.
base_fee = w3.eth.get_block("latest")["baseFeePerGas"]
priority = w3.to_wei(3, "gwei") # adjust for the chain
max_fee = base_fee * 2 + priority
tx = {
"to": acct.address, # send to yourself
"value": 0,
"nonce": nonce, # same nonce as the stuck transaction
"gas": 21000,
"maxFeePerGas": max_fee,
"maxPriorityFeePerGas": priority,
"chainId": w3.eth.chain_id,
"type": 2,
}
signed = acct.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction) # `rawTransaction` in web3.py v6
print("Replacement sent:", tx_hash.hex())
Make sure the new maxFeePerGas and maxPriorityFeePerGas are both higher than the stuck transaction’s values by at least the node’s bump threshold, otherwise you’ll get an error like replacement transaction underpriced.
Things that go wrong
nonce too low: the original was mined before your replacement. Nothing to cancel.replacement transaction underpriced: your fee bump wasn’t big enough. Raise both fee fields.- Several stuck transactions: fix the lowest nonce first. Once it’s mined, the others may go through on their own, or you replace them one by one.
- Different RPCs see different pools: a replacement sent to one provider may take time to reach others. Wait a little before resending.
Takeaways
Pending transactions aren’t magic. They’re a queue ordered by nonce, and you control the queue by reusing a nonce with a higher fee. If you’re building a product that sends transactions on users’ behalf, handle this automatically: track nonces yourself, watch for stuck transactions and replace them with a bounded fee bump.